三、文件系统:
1、目录和文件
2、系统数据文件和信息
3、进程环境
类ls的实现,如myls
/etc/group
/etc/passwd
一、目录和文件
1、获取文件属性
stat: 通过文件路径获取属性,面对符号链接文件时获取的是所指向的目标文件的属性。
fstat:通过文件描述符获取属性。
lstat:面对符号链接文件时获取符号链接文件的属性。
查看文件的属性:将参数path所指的文件状态复制到参数buf所指的结构中
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
返回值:成功,返回0; 失败, 返回-1,设置errno.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
off_t flen(const char *path)
{
struct stat statres;
if(stat(path,&statres) < 0)
{
perror("stat()");
exit(1);
}
return statres.st_size;
}
int main(int argc ,char **argv)
{
if(argc < 2)
{
fprintf(stderr,"Usage....\n");
exit(1);
}
printf("%lld\n",flen(argv[1]));
exit(0);
}
文件类型
普通文件 -
目录文件 d
字符设备文件 c
块设备文件 b
符号链接 l
网络套接字 s
管道(FIFO) p
2、文件访问权限
st_mode是一个16位的位图,用于表示文件类型,文件访问权限以及特殊权限位。
3、umask用来设定系统中新创建文件的默认权限的
文件掩码是linux系统中维护的一个全局设置
0666 & ~umask
作用:为进程设置文件权限(模式)创建掩码(屏蔽字),防止产生权限过松的文件。
修改进程的文件模式创建屏蔽字:mode_t umask(mode_t mask);
返回值:之前的文件模式创建屏蔽字。
修改屏蔽字为 mask&0777,返回就的屏蔽字
注意:进程的屏蔽字是继承于父进程,umask 不会改变父进程的屏蔽字
4、现有文件权限的更改/管理 chmod fchmod
把文件的权限设置为 mode
int chmod(const char *pathname, mode_t mode);
pathname参数为绝对路径
返回值:成功,返回0; 出错, 返回-1.
修改打开文件的访问权限
int fchmod(int fd, mode_t mode);
返回值:成功,返回0; 出错, 返回-1.
注意:chmod函数不受文件模式屏蔽字的约束,要想改变一个文件的权限位,进程的有效用户ID必须等于文件所有者ID或者进程的有效用户ID是0.
5、粘住位 t位
文件的文件模式中可以设置一个位,此位为粘住位S_ISVTX。即文件模式0777中的0。
如果在一个执行文件设置了该位,则执行该文件且进程结束后,系统会把该进程的正文部分放置磁盘的交换区中,
(在交换区中文件是连续存放的,不像非交换区一样,一个文件的内容分散在磁盘的几个块中.)
所以在加载该执行文件时就可以加快速度启动,直接从交换区中把进程的正文部分取至内存中运行。
6、文件系统:FAT,UFS
文件系统:文件或数据的存储和管理
FAT16/32 静态存储单链表
7、硬链接,符号链接 link ulink remove rename
区别与联系:
硬链接与目录是同义词,且建立硬链接有限制;不能给分区建立,不能给目录建立。
符号链接优点:可跨分区, 可以给目录建立。
硬链接:ln bigfile bigfile_link (给一个文件做了一个硬链接)
硬链接其实就是指向 i 节点的一个目录项,如果有多个目录项指向同一个i节点,
那么该i节点表示的文件就有多个链接,删除一个文件其实就是删除了一个目录项,
也就是解除了一个链接,当一个文件的链接数为 0 的时候,这个文件的实际内容才会被删除。
符号链接:符号链接是一个独立的文件,这个文件的内容是指向的文件的路径名。
ln -s bigfile_link bigfile_s
创建一个现有文件的链接: int link(const char *oldpath, const char *newpath);
返回值:成功,返回0; 出错, 返回-1.
参数oldpath表示现有文件,参数newpath表示新目录项。
解除一个已有文件的链接: int unlink(const char *pathname);
返回值:成功,返回0; 出错, 返回-1.
删除目录项,并将pathname所引用文件的链接计数减1.
unlink创建匿名文件
方法:open一个文件然后unlink, colse可以释放。
删除一个文件或目录:int remove(count char *pathname)
相当于rm
改变一个文件的名字或位置:int rename(count char *oldpath, count char *newpath)
返回值:成功,返回0; 出错, 返回-1.
相当于mv
8、utime:可以更改的最后一次读的时间和写的时间
9、目录的创建和销毁 mkdir rmdir
创建目录:int mkdir(const char *pathname, mode_t mode);
删除目录:int rmdir(const char *pathname);
打开目录:DIR *opendir(const char *name); /DIR *fdopendir(int fd);
关闭目录:int closedir(DIR *dirp);
读目录:struct dirent *readdir(DIR *dirp);
写目录:void rewinddir(DIR *dirp);
10、更改当前工作路径 chdir fchdir
1>更改工作路径
int chdir(const char *path);
int fchdir(int fd);
参数path或fd为新的当前工作目录。
返回值:成功,返回0;出错,返回-1.
2>获得当前工作路径
char *getcwd(char *buf, size_t size);
参数buf表示缓冲区地址,参数size表示缓冲区长度
返回值:成功,返回buf; 失败,返回NULL。
char *getwd(char *buf);
char *get_current_dir_name(void);
注意:如果 buf 的空间不够大,则会出错返回 NULL
注意:如果 getcwd 中的 buf 为 NULL,系统会分配 size 大小的内存,用户需要 free
注意:用户需要 free get_current 函数返回的内存
注意:工作路径是进程的属性,因此子进程调用 chdir 不会改变父进程的工作路径
11、分析目录/读取目录内容
du可以分析出来一个目录或一个文件所占磁盘空间的大小,以K为单位。
如:du 文件名/目录名
12、glob():解析模式/通配符
glob库函数用于Linux文件系统中路径名称的模式匹配,即查找文件系统中指定模式的路径。
注意,这不是正则表达式匹配,虽然有些相似,但还是有点差别。
glob函数原型
#include <glob.h>
int glob(const char *pattern, int flags,
int errfunc(const char *epath, int eerrno),
glob_t *pglob);
glob函数搜索匹配 函数pattern中的参数,
如:/*是匹配根文件下的所有文件(不包括隐藏文件,要找的隐藏文件需要从新匹配),然后会将匹配出的结果存放到 pglob,即第4个参数中,
第二个参数能选择匹配模式,如:是否排序,或者在函数第二次调用时,是否将匹配的内容追加到pglob中等,第3个参数是查看错误信息用,一般置为NULL;
#include <stdio.h>
#include <stdlib.h>
#include <glob.h>
#define PAT "/etc/a*.conf"
int err_func(const char *epath, int eerrno)
{
printf("%s:%s\n",epath,strerror(eerrno));
}
int main(int argc,char *argv[])
{
int i,err;
glob_t globres;
err = glob(PAT,0,NULL/*err_func*/,&globres);
if(err)
{
printf("err = %d\n",err);
exit(1);
}
for(i = 0 ; i < globres.gl_pathc; i++)
puts(globres.gl_pathv[i]);
globfree(&globres);
exit(0);
}
一个变量它的使用是单纯的在递归点之前,那么在这个变量可以优化到静态区去存放,甚至可以放到全局位置也没有关系。
opendir();
closedir();
readdir(3);
rewinddir();
seekdir();
telldir();
二、系统数据文件和信息
1、/etc/passwd(账户信息)
不同系统环境中的口令文件支持不一样。
1>读取口令文件:(查询用户信息)
struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);
返回值:成功:返回指针; 出错,返回NULL。
注意:这两个函数不可重入
struct passwd {
char *pw_name; /* username */
char *pw_passwd; /* user password */
uid_t pw_uid; /* user ID */
gid_t pw_gid; /* group ID */
char *pw_gecos; /* user information */
char *pw_dir; /* home directory */
char *pw_shell; /* shell program */
};
[组信息,函数,和加密原理讲解]
2、/etc/group(组信息)
1>查询组的相关信息
struct group *getgrnam(const char *name);
struct group *getgruid(uid_t uid);
返回值:成功:返回指针; 出错,返回NULL。
struct group {
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group ID */
char **gr_mem; /* NULL-terminated array of pointers
to names of group members */
};
3、/etc/shadow(加密口令)
普通用户下:cat /etc/shadow(无法查看权限不够)
(root下)
阴影文件格式
用户名:密码:最近修改时间:::
zhiyong:$6$mrFIWNAI$Iy788XD2BtJn9gImM9cyZZCQPHP7.OE8RzLzyalwM1WfAcuiP20nW
4Zfrackk6Qzq10eOze5BEWAMb1NsSA371:16295:0:99999:7:::
6表示加密方式 mrFIWNAI表示杂字串(原串或上杂字经过加密的到Iy788XD2BtJn9gImM9cyZZCQPHP7.OE8RzLzyalwM1WfAcuiP20nW
4Zfrackk6Qzq10eOze5BEWAMb1NsSA371)
1>通过阴影密码文件API
struct spwd *getspnam(const char *name);
返回值:成功:返回指针; 出错,返回NULL。
2>密码和数据加密
char *crypt(const char *key, const char *salt);
key表示口令原文
返回值:成功:返回加密密码的指针; 出错,返回NULL。
hash: 混淆(不可逆) 如果原串相同,所得串也相同,需防备管理员监守自盗
加密:安全:攻击成本大于收益
安全? 穷举:口令随机校验
//[密码校验实例]
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<shadow.h>
#include<string.h>
int main(int argc, char **argv)
{
char *input_pass;
struct spwd *shadowline;
char *crypted_pass;
if(argc < 2)
{
fprintf(stderr, "Usage...\n");
exit(1);
}
input_pass = getpass("Password:");//获取口令 password提示符
shadowline = getspnam(argv[1]);//访问口令文件获取所有信息
crypted_pass = crypt(input_pass, shadowline->sp_pwdp);//加密
if(strcmp(shadowline->sp_pwdp,crypted_pass) == 0)
puts("OK!");
else
puts("faile!");
exit(0);
}
4、时间戳
[时间函数精讲]
1>得到以秒为单位的时间
time_t time(time_t *t);
返回值:返回 UTC 时间(从 1970-01-01 00:00:00 开始的秒数); 出错, 返回-1
2>将日历时间转换成协调统一时间的年月日十分秒周日分解结构
struct tm *gmtime(const time_t *timep);
返回值:指向分解的tm结构的指针; 出错, 返回NULL。
3>将日历时间转换成本地时间
struct tm *localtime(const time_t *timep);
返回值:指向分解的tm结构的指针; 出错, 返回NULL。
4>以本地时间的年月日等作为参数,将其变换成time_t值
time_t mktime(struct tm *tm);
返回值:成功返回日历时间,出错返回-1.
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};
5>类似于printf的时间值函数。可以通过可用的多个参数来制定产生的字符串。
size_t strftime(char *s, size_t max, const char *format,const struct tm *tm);
s表示所放内容的位置,空间大小为max,format为格式串,从tm中抽取
返回值:若有空间,返回存入数组的字符数; 否则,返回0.
[时间实例1]
//获取一个时间一秒钟向终端打印一次
//同时具有过一段时间依然能获取当前时间输出
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define FNAME "/tmp/out"
#define BUFSIZE 1024
int main()
{
struct tm *tm;
time_t stamp;
int no;
char buf[BUFSIZE];
FILE *fp;
fp = fopen(FNAME,"a+");//打开FANME文件 用追加读写
if(fp == NULL)
{
perror("fopen()");
exit(1);
}
while(fgets(buf,BUFSIZE,fp) != NULL)//从fp读取内容到buf中,字节大小BUFSIZE
no++;
while(1)
{
time(&stamp);//获取时戳
tm = localtime(&stamp);//转换成结构体
fprintf(fp,"%-4d%d-%d-%d %d:%d:%d\n",
++no,tm->tm_year+1900,tm->tm_mon+1,
tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec);
//no自增换行
fflush(fp);
sleep(1);
}
fclose(fp);
exit(0);
}
[]$ ./timelog
切换终端执行
[]$ tail -f /tmp/out
[时间函数实例2]
//一年任意一天的一百天后是什么时间
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define BUFSIZE 1024
int main()
{
struct tm *tm;
time_t stamp;
char buf[BUFSIZE];//缓冲区
stamp = time(NULL);//获取时戳
tm = localtime(&stamp);//转换成结构体
strftime(buf,BUFSIZE,"Now:%Y-%m-%d",tm);
//显示当前年月日,从tm中抽取放到buf中,空间大小为BUFSIZE。
puts(buf);//标准输出
tm->tm_mday += 100;
mktime(tm);//可以自动将时间进位。
strftime(buf,BUFSIZE,"100 days later :%Y-%m-%d",tm);
puts(buf);
exit(0);
}
三、进程环境
[进程终止方式]
1、main函数
int main(int argc, char *argv[])
argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组
2、进程的终止
正常终止:
1从main函数返回;
注意:在 main 函数中调用 return 0 和 exit(0)是等价的
2调用exit;
使当前进程正常终止:void exit(int status);
3调用_exit或_Exit;
void _exit(int status);
void _Exit(int status);
注意:exit 函数会调用 fclose 关闭所有打开的流,并且会调用由 atexit 安装的函数,另外两个函数会直接进入内核,不会清理缓存。
4最后一个线程从其启动例程返回;
5最后一个线程调用pthread_exit。
异常终止:
1调用abort;
2接到一个信号并终止;
3最后一个线程对其取消请求作出响应。
钩子函数:atexit()
int atexit(void (*function)(void));
安装若干个在调用 exit 函数时候调用的函数 function,注册一个在正常进程终止时调用的函数。
注意:每个进程可以安装多达 32 个函数,同一个函数也可以被安装多次,执行的顺序和安装的顺序相反。
[钩子函数]
#include<stdlib.h>
#include<stdio.h>
static void f1(void)
{
puts("f1() is working!");
}
static void f2(void)
{
puts("f2() is working!");
}
static void f3(void)
{
puts("f3() is working!");
}
int main()
{
puts("Begin!");
atexit(f1);
atexit(f2);
atexit(f3);
puts("End!");
exit(0);
}
/*****************
Begin!
End!
f3() is working!
f2() is working!
f1() is working!
******************/
3、[命令行参数的分析]
解析命令行参数:int getopt(int argc, char * const argv[],const char *optstring);
[命令行实例2]
//解析时间
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#define SIZE 1024
int main(int argc,char **argv)
{
struct tm *tm;
time_t stamp;
char buf[SIZE];
int ch;
char fmt[SIZE] = {'\0'};//格式串 空串
FILE *fp = stdout;
stamp = time(NULL);//获取时间戳
tm = localtime(&stamp);//转换为结构体形式
while(1)
{
ch = getopt(argc,argv,"-y:mdH:MS");
//解析命令行参数-y:mdH:MS -表示识别非选项的传参 :表示这个字母在终端上使用传参的话,它有自己的参数,这叫带参数的选项
if(ch < 0)
break;
switch(ch)
{
case 1:
if(fp == stdout)
{
fp = fopen(argv[optind-1],"w");
if(fp == NULL)
{
perror("fopen()");
fp = stdout;
}
}
break;
case 'y':
if(strcmp(optarg,"2") == 0)
strncat(fmt,"%y ",SIZE);
else if(strcmp(optarg,"4") == 0)
strncat(fmt,"%Y ",SIZE);
else
fprintf(stderr,"Invalid argument of -y\n");
break;
case 'm':
strncat(fmt,"%m ",SIZE);
break;
case 'd':
strncat(fmt,"%d ",SIZE);
break;
case 'H':
if(strcmp(optarg,"12") == 0)
strncat(fmt,"%I(%P) ",SIZE);
else if(strcmp(optarg,"24") == 0)
strncat(fmt,"%H ",SIZE);//向fmt中连进去一个大小为SIZE的%H,覆盖fmt中的'\0'。
else
fprintf(stderr,"Invalid argument of -H\n");
break;
case 'M':
strncat(fmt,"%M ",SIZE);//向fmt中连进去一个大小为SIZE的%M,覆盖fmt中的'\0'。
break;
case 'S':
strncat(fmt,"%S ",SIZE);//向fmt中连进去一个大小为SIZE的%S,覆盖fmt中的'\0'。
break;
default:
break;
}
}
strncat(fmt,"\n",SIZE);
strftime(buf,SIZE,fmt,tm);//按照fmt格式从tm中拿出内容放到大小为SIZE的buf中。
fputs(buf,fp);
if(fp != stdout)
fclose(fp);
exit(10);
}
4、[环境变量]
每个进程都有一组环境变量,进程的环境变量继承于父进程
指向进程环境表的指针是 char **environ
环境表是一个指针数组,数组中的每一个指针都指向一个字符串(环境变量)
查看环境变量:export
KEY = VAILE
environ存放所有的环境变量
1>获得环境变量:char *getenv(const char *name);
返回值:指向与name关联的value的指针;若未找到,返回NULL
注意:如果在环境变量当中没有 name,那么该函数可能会导致段错误。
(获取环境变量)
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main()
{
puts(getenv("PWD"));//获取当前路径
/*
int i;
for(i = 0 ; environ[i] != NULL ; i++)//获取所有环境变量
puts(environ[i]);
*/
exit(0);
}
2>修改或添加一个环境变量的值:int putenv(char *string);
返回值:成功,返回0;出错,返回非0.
putenv取形式为name=value的字符串,将其放到环境表中。如果name已经存在,则先删除其原来的定义。
3>int setenv(const char *name, const char *value, int overwrite);
返回值:成功,返回0;出错返回-1.
name表示环境变量的名字 name存在相当与add,name不存在相当于change value=name overwrite表示是否覆盖。
setenv将name设置为value,如果在环境中name已经存在,那么:
(a)若overwrite非0,则首先删除其现有的定义;
(b)若overwrite为0,则不删除其现有定义(name不能设置为新的value,而且也不出错)。
注意:用 putenv 添加或者修改环境变量时,系统并不分配空间给 name,只是把 name 添加或者更新到环境表中,
所以只要修改了 name 指向的字符串就修改了环境变量,如果 name指向的空间是在某个函数的栈中分配的,一旦函数返回就可能出现问题.
4>删除环境变量
int unsetenv(const char *name);
返回值:成功,返回0;出错返回-1.
5、c程序的存储空间布局
[程序空间和手工装载库]
pmap(1)
内存分配
void *malloc(size_t size);
void free(void *ptr);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
6、库
动态库
静态库
手工装载库
void *dlopen(const char *filename, int flags);
filename表示要手工装载库的名字 flags表示打开的模式
返回值:成功返回一个非空指针;出错返回NULL。
int dlclose(void *handle);
返回值:成功返回0;出错返回非零值。
为动态加载的应用程序界面中的函数获取错误诊断:char *dlerror(void);
在共享对象或可执行文件中获得一个符号的地址:void *dlsym(void *handle, const char *symbol);
返回值:成功,返回与符号关联的地址。失败时,返回NULL。
[手工装载库并打印cosine2.0]
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <gnu/lib-names.h>
int main(void)
{
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen(LIBM_SO, RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror(); /* Clear any existing error */
cosine = (double (*)(double)) dlsym(handle, "cos");
//*(void **) (&cosine) = dlsym(handle, "cos");
error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf("%f\n", (*cosine)(2.0));
dlclose(handle);
exit(EXIT_SUCCESS);
}
7、函数跳转
goto不能跨函数跳转
长跳转
用 setjmp 设立一个标志,用 longjmp 跳到该标志处,setjmp 调用时返回 0,longjmp 的调用
会使 setjmp 再一次返回,返回值是 val。
1>设置跳转点:int setjmp(jmp_buf env);
返回值:若直接调用,返回0;若从longjmp返回,则为非0.
2>返回跳转点:void longjmp(jmp_buf env, int val);
env为设置的跳转点,val为带会的东西。
注意:longjmp 会涉及到变量的回滚,在不指定优化条件的前提下,自动变量、静态变量、易失性变量、寄存器变量、全局变量都不会回滚(因为这些变量都放到内存中),
但是在指定了-O3 优化的情况下,自动变量和寄存器变量会被放到寄存器中,因此会回滚。
注意:如果多次用同一个 env 调用 setjmp 函数,则最后一次有效。
(跳转实例)
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
static jmp_buf save;
void d(void)
{
printf("[%s]Begin\n",__FUNCTION__);
printf("[%s]Jump now!\n",__FUNCTION__);
longjmp(save,6);
printf("[%s]End\n",__FUNCTION__);
}
void c(void)
{
printf("[%s]Begin\n",__FUNCTION__);
printf("[%s]call d()\n",__FUNCTION__);
d();
printf("[%s]d() returned.\n",__FUNCTION__);
printf("[%s]End\n",__FUNCTION__);
}
void b(void)
{
printf("[%s]Begin\n",__FUNCTION__);
printf("[%s]call c()\n",__FUNCTION__);
c();
printf("[%s]c() returned.\n",__FUNCTION__);
printf("[%s]End\n",__FUNCTION__);
}
void a(void)
{
int ret;
printf("[%s]Begin\n",__FUNCTION__);
ret = setjmp(save);
if(ret == 0)//set
{
printf("[%s]call b()\n",__FUNCTION__);
b();
printf("[%s]b() returned.\n",__FUNCTION__);
}
else
{
printf("[%s]Jumped back here with code %d.\n",__FUNCTION__,ret);
}
printf("[%s]End\n",__FUNCTION__);
}
int main()
{
printf("[%s]Begin\n",__FUNCTION__);
printf("[%s]call a()\n",__FUNCTION__);
a();
printf("[%s]a() returned.\n",__FUNCTION__);
printf("[%s]End\n",__FUNCTION__);
exit(0);
}
/*
[main]Begin
[main]call a()
[a]Begin
[a]call b()
[b]Begin
[b]call c()
[c]Begin
[c]call d()
[d]Begin
[d]Jump now!
[a]Jumped back here with code 6.
[a]End
[main]a() returned.
[main]End
*/
8、资源的获取及控制
获得进程资源限制:int getrlimit(int resource, struct rlimit *rlim);
修改进程资源限制:int setrlimit(int resource, const struct rlimit *rlim);
返回值:成功返回0; 出错返回-1,并设置error。
struct rlimit {
rlim_t rlim_cur; /* Soft limit */
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};
注意: rlim.rlim_cur 是当前的限制,任何进程都可以修改(但必须小于等于 rlim.rlim_max),
rlim.rlim_max 是一个限制的最大值,只有超级用户进程才可以提高这个值