主要内容如下:
一、目录和文件
1、获取文件属性
2、文件访问权限
3、umask
4、文件权限的更改/管理
5、粘住位
6、文件系统:FAT,UFS
7、硬链接,符号链接
8、utime
9、目录的创建和销毁
10、更改当前工作路径
11、分析目录/读取目录内容
二、系统数据文件和信息
1、用户信息相关
2、组信息相关
3、时间戳
三、进程环境
1、main函数
2、进程的终止
3、命令行参数的分析
4、环境变量
5、库
6、函数跳转
7、资源的获取及控制
一、目录和文件
1、获取文件属性:stat、fstat、lstat。(系统调用)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct *buf);
int lstat(const char *path, struct stat *buf);
//查看文件的属性写入buf中然后传出,成功返回0,失败返回-1并写入errno
//这个地方其实可以看出系统调用的一些函数的命名规则,stat传入的是路径,fstat(前缀是f)传入的是文件描述符,而lstat和stat看上去一样,但是这个l前缀我们猜测也是关于link链接之类的东西:stat函数是穿透(追踪)函数,即对符号链接文件进行操作时,操作的是链接到的那一个文件,不是符号链接文件本身;而lstat函数是不穿透(不追踪)函数,对符号链接文件进行操作时,操作的是符号链接文件本身。
struct stat里面的参数:
struct stat{
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
这里写一个计算文件大小的程序来了解下stat的使用方式,其实通过上个部分(i/o)的讲解已经可以用例如fgetc/fputc等函数来实现读取文件的字节数,这里我们使用stat来实现:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
//用例:./flen FILENAME
static off_t flen(const char *filename){
struct stat statres;
if(stat(filename,&statres) < 0){
perror("stat()");
exit(1);
}
return statres.st_size;
}
int main(int argc, char **argv){
if(argc != 2){
fprintf(stderr,"please use right argc!\n");
exit(1);
}
printf("%ld\n",flen(argv[1]));//注意看man手册中stat中的st_size属性是off_t类型,所以这里使用long接收,尽量别使用int
exit(0);
}
运行结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ll -a
total 40
drwxrwxr-x 2 liugenyi liugenyi 4096 3月 9 14:05 ./
drwxrwxr-x 4 liugenyi liugenyi 4096 3月 9 13:39 ../
-rwxrwxr-x 1 liugenyi liugenyi 17056 3月 9 14:05 flen*
-rw-rw-r-- 1 liugenyi liugenyi 624 3月 9 14:05 flen.c
-rw-rw-r-- 1 liugenyi liugenyi 2424 3月 9 14:05 flen.o
-rw-rw-r-- 1 liugenyi liugenyi 202 3月 9 14:03 makefile
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./flen makefile
202
我们再回到stat结构体当中的这三个属性:
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
有些人可能认为和windows一样,st_blksize(块大小)st_blocks(块个数)== st_size,但其实这是不对的,在Linux中我们可以验证下,下面我们先生成一个空洞文件(上一个专题讲过),然后看看这个空洞文件的文件属性:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
//用例格式:./big FILENAME
int main(int argc, char **argv){
int fd;
if(argc != 2){
fprintf(stderr,"please use right argc!\n");
exit(1);
}
fd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0600);
if(fd < 0){
perror("open()");
exit(1);
}
lseek(fd,5LL * 1024LL * 1024LL * 1024LL - 1LL, SEEK_SET);//注意,这里如果直接写5*1024*1024*1024-1编译会出现整形溢出警告,但其实这是错误
//就算写成(5*1024*1024*1024-1)LL也不行,因为在转换之前的计算就已经发生溢出了
//后面-1是因为还要用一次系统调用写进去一个
write(fd,"",1);
close(fd);
exit(0);
}
我们编译运行看看效果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ make big
gcc -c -o big.o big.c
gcc big.o -o big
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./big bigfile
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ stat bigfile
File: bigfile
Size: 5368709120 Blocks: 8 IO Block: 4096 regular file
Device: 805h/2053d Inode: 800101 Links: 1
Access: (0600/-rw-------) Uid: ( 1000/liugenyi) Gid: ( 1000/liugenyi)
Access: 2024-03-09 21:24:09.072034950 +0800
Modify: 2024-03-09 21:24:06.172468829 +0800
Change: 2024-03-09 21:24:06.172468829 +0800
Birth: -
可以看到 Blocks: 8,只一个5g的空洞文件只占用了4k大小。
2、文件访问权限:st_mode
我们在回到stat结构体中,注意看st_mode属性。st_mode是一个16位的位图,用于表示文件类型,文件访问权限,以及特殊权限位
mode_t st_mode; /* File type and mode */
这里我们写个简单的程序来判断一个文件的类型:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
// 用例:./filetype FILENAME
static int getfiletype(const char *filename)
{
struct stat statres;
if (stat(filename, &statres) < 0)
{
perror("stat()");
exit(1);
}
if (S_ISREG(statres.st_mode))
{ // 这个宏表示如果该文件是普通文件,返回真
return '-';
}
else if (S_ISDIR(statres.st_mode))
{ // 这个宏表示如果该文件是目录文件,返回真
return 'd';
}
else if (S_ISSOCK(statres.st_mode))
{ // 这个宏表示如果该文件是套接字socket文件(后面网络编程详细说),返回真
return 's';
}
else
{ // 当作出异常,这里并不是只有三种文件类型,还有管道文件快文件等等,这里只举了几个例子,完整的可以看man手册
return '!';
}
}
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "please use right argc!\n");
exit(1);
}
printf("%c\n", getfiletype(argv[1]));
exit(0);
}
结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ make filetype
gcc -c -o filetype.o filetype.c
gcc filetype.o -o filetype
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./filetype filetype.c
-
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./filetype ../fs
d
除了上面我用宏的方式判断文件类型,还可以使用和进制按位与来判断,数值参照man手册
3、umask
作用:主要是为了防止产生权限过松的问题
公式:文件的权限 = 0666(默认)&(按位与) ~umask(umask取反)
umask本身是个命令,用于查看当前的umask值(默认0002),也可修改,修改方式:umask 修改后的值。
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ umask
0002
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ umask 0020
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ umask
0020
其次,umask也有一个对应的系统调用,你也可以在进程中去修改他
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
//mask:八进制位的掩码值
//返回之前的掩码值,没有失败只有成功。
4、文件权限的更改/管理:chmod/fchmod
chmod/fchmod本身是个命令,也是系统调用,首先看看他是命令时的用法:
第一种方式:666表示(110 110 110),664表示(110 110 100)对应的是(rwx rwx rwx)中的1就是有权限,0则无,从高三位到低三位依次表示:拥有者、同组、其他用户;r(读权限)、w(写)、x(执行)
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ll -a big
-rw-rw-r-- 1 liugenyi liugenyi 16976 3月 9 21:23 big
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ chmod 666 big
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ll -a big
-rw-rw-rw- 1 liugenyi liugenyi 16976 3月 9 21:23 big
或者用第二种方式:o+w表示给其他用户(other)加上一个写(w)权限
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ll -a big
-rw-rw-r-- 1 liugenyi liugenyi 16976 3月 9 21:23 big
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ chmod o+w big
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ll -a big
-rw-rw-rw- 1 liugenyi liugenyi 16976 3月 9 21:23 big
系统调用中的chmod/fchmod如下:
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
//pathname:文件名
//mode:修改后的权限
int fchmod(int fd, mode_t mode);
//pathname:文件描述符(对应上面我提到的linux里命名规律,加了f前缀一般传入的参数就从文件名变成了文件描述符fd)
//mode:修改后的权限
5、粘住位(t位)
是Unix系统中用于文件和目录的一个特殊权限位。在文件系统中,当一个目录设置了粘住位后,只有对该目录具有写权限的用户才能删除或更名该目录下的文件。粘住位的设置可以防止未授权的用户删除或修改重要文件。典型的就是系统文件夹: /tmp (drwxrwxrwt root root)
如何设置粘住位:(假设file权限为666)
chmod 1666 file
chmod +t file
6、文件系统:FAT,UFS
什么是文件系统:文件或者数据的存储和管理
FAT16 FAT32文件系统优点、缺点及功能限制:
单个文件支持大小:FAT16文件系统最大支持2g文件,FAT32文件系统最大支持4g文件。
FAT32文件系统优点:它的兼容性比较高。
FAT32文件系统缺点:它不支持单个文件大于4g的文件传输和保存,无文件压缩及文件加密等功能,数据地址占用比较偏高。
UFS文件系统的优缺点:
优点:由于出现了多级指针,因此不怕大文件。
缺点:很多人说UFS文件系统不善于或者不能管理小文件。我们每次产生一个新文件,都会分配一个新的 inode,位于inode块区域,那么就会有一种情况,当inode块区域中的所有inode都被耗尽了,但是块区域还存在大量的空白块没有被使用,这种情况就是大量的小文件造成的,有些文件几乎不占磁盘空间,如符号链接文件等,但是也要分配inode。这样的话当在使用某个文件的时候,就会在 inode区域,即inode结构体数组中查找目标inode结构体,这样查找就很耗时。
7、硬链接,符号链接
Linux 链接分两种,一种被称为硬链接,另一种被称为符号链接。默认情况下,ln 命令产生硬链接。
我们可以把硬链接理解成源文件的副本,他和源文件一样的大小。 符号链接可以理解为类似 windows 一样的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,这个文件包含了另一个文件的路径名。
我们看看效果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ln big.c big.c_link
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ stat big.c
File: big.c
Size: 836 Blocks: 8 IO Block: 4096 regular file
Device: 805h/2053d Inode: 789039 Links: 2
Access: (0664/-rw-rw-r--) Uid: ( 1000/liugenyi) Gid: ( 1000/liugenyi)
Access: 2024-03-09 21:23:44.988134347 +0800
Modify: 2024-03-09 21:23:44.952134465 +0800
Change: 2024-03-10 18:40:23.290062626 +0800
Birth: -
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ stat big.c_link
File: big.c_link
Size: 836 Blocks: 8 IO Block: 4096 regular file
Device: 805h/2053d Inode: 789039 Links: 2
Access: (0664/-rw-rw-r--) Uid: ( 1000/liugenyi) Gid: ( 1000/liugenyi)
Access: 2024-03-09 21:23:44.988134347 +0800
Modify: 2024-03-09 21:23:44.952134465 +0800
Change: 2024-03-10 18:40:23.290062626 +0800
Birth: -
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ln -s big.c big.c_links
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ stat big.c_links
File: big.c_links -> big.c
Size: 5 Blocks: 0 IO Block: 4096 symbolic link
Device: 805h/2053d Inode: 800111 Links: 1
Access: (0777/lrwxrwxrwx) Uid: ( 1000/liugenyi) Gid: ( 1000/liugenyi)
Access: 2024-03-10 18:40:53.258035198 +0800
Modify: 2024-03-10 18:40:53.142035305 +0800
Change: 2024-03-10 18:40:53.142035305 +0800
Birth: -
从上面的结果中可以看出,硬连接文件 big.c_link与原文件 big.c的 inode 节点相同,均为 789039,然而符号连接文件的 inode 节点不同(800111)。
如果我删除big.c文件,那么硬链接(big.c_link)文件不会受影响,但是符号链接文件(big.c_links)失效。
除了命令以外,也有对应的系统调用:
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
int unlink(const char *pathname);
8、utime
可以更改文件最后读的时间和最后修改的时间(不是很常用)
#include <sys/types.h>
#include <utime.h>
int utime(const char *filename, const struct utimbuf *times);
//filename:待修改文件
//utime:见下面结构体
struct utimbuf {
time_t actime; /* access time */
time_t modtime; /* modification time */
};
9、目录的创建和销毁:mkdir/rmdir
这两个命令也有相应的系统调用:
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
//pathname:目录
//mode:权限
#include <unistd.h>
int rmdir(const char *pathname);
//pathname:目录
//注意,删除的目录必须为空
10、更改当前工作路径
说到更改当前工作路径,我们立马想到了cd命令,它其实就是用系统调用chdir封装的,介绍如下:
#include <unistd.h>
//更改当前工作路径
int chdir(const char *path);
//path:切换后的路径
int fchdir(int fd);
//fd:你成功打开的路径
char *getcwd(char *buf, size_t size);//获取当前工作路径(把当前目录的绝对路径写入到buf,size是buf的长度)
这里简单写个getcwd例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFSIZE 1024
int main()
{
char buf[BUFSIZE];
getcwd(buf, BUFSIZE);
printf("%s\n", buf);
exit(0);
}
结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ make getpath
gcc -c -o getpath.o getpath.c
gcc getpath.o -o getpath
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./getpath
/home/liugenyi/linuxsty/fs
11、分析目录/读取目录内容
glob:解析模式/通配符
#include <glob.h>
int glob(const char *pattern, int flags,int (*errfunc) (const char *epath, int eerrno),glob_t *pglob);
//pattern:待解析的模式/通配符
//flags:追加的可选参数,不需要则写0即可,参考man手册即可
//errfunc用于设置epath(出错路径)和eerrno(出错码)函数,可以用于打印出错路径等等,不需要的话这个参数则写NULL即可
//pglob:用于存放解析结果,结构体内容如下:
/*
typedef struct {
size_t gl_pathc; //Count of paths matched so far 这个参数就类似于argc
char **gl_pathv; //List of matched pathnames. 这个参数就类似于argv
size_t gl_offs; //Slots to reserve in gl_pathv.
} glob_t;
*/
void globfree(glob_t *pglob);//glob中申请了空间,这里用于释放
下面写个例子来实现一个简单的解析功能:
#include <stdio.h>
#include <stdlib.h>
#include <glob.h>
#include <string.h>
#define pat "./big*" // 解析当前路径下所有以big开头的文件
static int errfunc(const char *epath, int eerrno)
{
puts(epath);
fprintf(stderr, "error msg:%s\n", strerror(eerrno));
return 0;
}
int main(int argc, char **argv)
{
int err;
glob_t pglobres;
err = glob(pat, 0, errfunc, &pglobres);//这里flags没有什么需求,所以写0,如果第三个参数也不需要,写NULL即可
if (err)
{ // glob成功时返回0,否则则返回对应的错误码
printf("glob() failed! errcode = %d\n", err);
exit(1);
}
for (int i = 0; i < pglobres.gl_pathc; ++i)
{
printf("%s\n", pglobres.gl_pathv[i]);
}
globfree(&pglobres);
exit(0);
}
运行结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ls ./big*
./big ./big.c ./big.c_link ./big.c_links ./big.o
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./glob
./big
./big.c
./big.c_link
./big.c_links
./big.o
除了glob,我们还可以使用几个函数来实现上面的功能
opendir/fopendir、closedir、readdir
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);//打开一个目录流
DIR *fdopendir(int fd);
//其实这里的这些操作可以类比之前的文件操作
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);//关闭一个目录流
#include <dirent.h>
struct dirent *readdir(DIR *dirp);//返回的结构体如下:
/*
struct dirent {
ino_t d_ino; //Inode number
off_t d_off; //Not an offset; see below
unsigned short d_reclen; //Length of this record
unsigned char d_type; //Type of file; not supported
by all filesystem types
char d_name[256]; //Null-terminated filename
};
*/
这里我们使用这几个函数来重构上面的glob函数实现的功能:
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#define pat "./" // 解析当前路径下所有文件
int main()
{
struct dirent *direntres;
DIR *dir;
dir = opendir(pat);
if (dir == NULL)
{
perror("opendir()\n");
exit(1);
}
while ((direntres = readdir(dir)) != NULL)
{
printf("%s %lu\n", direntres->d_name, direntres->d_ino);
}
closedir(dir);
exit(0);
}
运行结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./readdir
readdir.o 800158
glob 800137
flen.o 800059
filetype 800109
big.c_link 789039
makefile 800057
glob.c 789267
filetype.o 800108
. 797129
glob.o 800136
readdir 800159
getpath.c 800112
big 800100
flen 800060
readdir.c 800102
getpath 800120
big.o 800099
big.c_links 800111
getpath.o 800119
.. 786932
flen.c 797164
filetype.c 792560
big.c 789039
du
du是个命令,后面跟目录或者文件可以看它所占的磁盘空间大小,单位k。例如:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ du .
176 .
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ du glob.c
4 glob.c
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ stat glob.c
File: glob.c
Size: 708 Blocks: 8 IO Block: 4096 regular file
Device: 805h/2053d Inode: 789267 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/liugenyi) Gid: ( 1000/liugenyi)
Access: 2024-03-10 22:01:24.023565620 +0800
Modify: 2024-03-10 22:01:24.007565631 +0800
Change: 2024-03-10 22:01:24.007565631 +0800
Birth: -
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ du ..
176 ../fs
176 ../iostudy
356 ..
可以看到du glob.c这个文件占4k,stat glob.c也是4k(8 blocks * 512),注意,du目录会包含目录本身的大小,比如这里du ..,包含了..本身的大小
下面我们自己来实现一个mydu:
#include <stdio.h>
#include <stdlib.h>
#include <glob.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#define PATHBUFSIZE 1024
static long long mydu(const char *path)
{
struct stat statres;
char pathbuf[PATHBUFSIZE];
glob_t globres;
int i;
long long sum;
if (lstat(path, &statres) < 0)
{
fprintf(stderr, "lstat() failed!\n");
exit(1);
}
if (!S_ISDIR(statres.st_mode))
{ // 如果不是目录文件(即一般文件),直接返回文件大小
return statres.st_blocks; // 这里其实statres.st_blocks/2才是文件的大小(单位k),因为其他地方也会返回,所以我统一放在主函数来除以2
}
// 否则则是目录文件,进行递归操作
// 先通过glob来解析当前目录下的文件或者目录,再组合路径进行递归操作
strncpy(pathbuf, path, PATHBUFSIZE); // 复制当前的path
strcat(pathbuf, "/*"); // 追加后缀
if (glob(pathbuf, 0, NULL, &globres)) // glob出错
{
fprintf(stderr, "glob() failed!\n");
globfree(&globres);
exit(1);
}
strncpy(pathbuf, path, PATHBUFSIZE); // 复制当前的path
strcat(pathbuf, "/.*"); // 追加后缀
if (glob(pathbuf, GLOB_APPEND, NULL, &globres)) // glob出错,这里的glob调用就用到了第二个参数flags里的追加
{
fprintf(stderr, "glob() failed!\n");
globfree(&globres);
exit(1);
}
sum = 0;
for (i = 0; i < globres.gl_pathc; ++i)
{
sum += mydu(globres.gl_pathv[i]);
}
sum += statres.st_blocks; // 还要加上目录本身的大小
globfree(&globres);
return sum;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "please use right argc!\n");
exit(1);
}
printf("%lld\n", mydu(argv[1]) / 2);
exit(0);
}
我们分别找个普通文件和目录文件运行测试下:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ make mydu
gcc -c -o mydu.o mydu.c
gcc mydu.o -o mydu
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ du mydu
20 mydu
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./mydu mydu
20
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./mydu ..
glob() failed!
但是我们到,当我测试目录文件的时候,出错了,这里虽然报错是glob,那是因为上面程序我对glob成功与否进行了判断,它实际的错误其实是空间已经不足,递归没有结束导致爆炸。我们把上面对glob的判断注掉可能看着会更清楚:
#include <stdio.h>
#include <stdlib.h>
#include <glob.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#define PATHBUFSIZE 1024
static long long mydu(const char *path)
{
struct stat statres;
char pathbuf[PATHBUFSIZE];
glob_t globres;
int i;
long long sum;
if (lstat(path, &statres) < 0)
{
fprintf(stderr, "lstat() failed!\n");
exit(1);
}
if (!S_ISDIR(statres.st_mode))
{ // 如果不是目录文件(即一般文件),直接返回文件大小
return statres.st_blocks; // 这里其实statres.st_blocks/2才是文件的大小(单位k),因为其他地方也会返回,所以我统一放在主函数来除以2
}
// 否则则是目录文件,进行递归操作
// 先通过glob来解析当前目录下的文件或者目录,再组合路径进行递归操作
strncpy(pathbuf, path, PATHBUFSIZE); // 复制当前的path
strcat(pathbuf, "/*"); // 追加后缀
// if (glob(pathbuf, 0, NULL, &globres))
// { // glob出错
// fprintf(stderr, "glob() failed!\n");
// globfree(&globres);
// exit(1);
// }
strncpy(pathbuf, path, PATHBUFSIZE); // 复制当前的path
strcat(pathbuf, "/.*"); // 追加后缀
glob(pathbuf, 0, NULL, &globres);
glob(pathbuf, GLOB_APPEND, NULL, &globres);
// if (glob(pathbuf, GLOB_APPEND, NULL, &globres))
// { // glob出错,这里的glob调用就用到了第二个参数flags里的追加
// fprintf(stderr, "glob() failed!\n");
// globfree(&globres);
// exit(1);
// }
sum = 0;
for (i = 0; i < globres.gl_pathc; ++i)
{
sum += mydu(globres.gl_pathv[i]);
}
sum += statres.st_blocks; // 还要加上目录本身的大小
globfree(&globres);
return sum;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "please use right argc!\n");
exit(1);
}
printf("%lld\n", mydu(argv[1]) / 2);
exit(0);
}
再次运行:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./mydu ..
Segmentation fault (core dumped)
这样就很清楚看到段错误对吧。那么问题出在哪里呢,为什么递归没有结束呢?我们仔细看,如果是普通文件的话没有问题,但是目录文件我们知道如果用ls -a可以看到有.和..文件,分别表示当前和上一级,那再看看我们的逻辑,只是进行了简单的追加,也就是说每一次递归都会加入.(因为上一次加入后我们会看它的子目录,存在.和..,又会把.加入,无限下去)因此,我们应该对递归的时候向mydu里传入的参数进行判断。下面更新下这个错误:
#include <stdio.h>
#include <stdlib.h>
#include <glob.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#define PATHBUFSIZE 1024
static int path_noloop(const char *path)
{
// 注意路径可能会带很长的前缀,比如/a/b/c/d/f/.
// 因此我们在判断最后一个位置是否是.或者..的时候,可以用到strchr,这个函数可以返回指定串的指定字符的位置
// 但是这里'/'只会出现在最右边,因此我们可以用strrchr,这个函数可以从最右边开始找
char *pos;
pos = strrchr(path, '/');
if (pos == NULL) // 如果没找到
{
exit(1);
}
if (strcmp(pos + 1, ".") == 0 || strcmp(pos + 1, "..") == 0)
{
return 0;
}
return 1;
}
static long long mydu(const char *path)
{
struct stat statres;
char pathbuf[PATHBUFSIZE];
glob_t globres;
int i;
long long sum;
if (lstat(path, &statres) < 0)
{
fprintf(stderr, "lstat() failed!\n");
exit(1);
}
if (!S_ISDIR(statres.st_mode))
{ // 如果不是目录文件(即一般文件),直接返回文件大小
return statres.st_blocks; // 这里其实statres.st_blocks/2才是文件的大小(单位k),因为其他地方也会返回,所以我统一放在主函数来除以2
}
// 否则则是目录文件,进行递归操作
// 先通过glob来解析当前目录下的文件或者目录,再组合路径进行递归操作
strncpy(pathbuf, path, PATHBUFSIZE); // 复制当前的path
strcat(pathbuf, "/*"); // 追加后缀
if (glob(pathbuf, 0, NULL, &globres)) // glob出错
{
fprintf(stderr, "glob() failed!\n");
globfree(&globres);
exit(1);
}
strncpy(pathbuf, path, PATHBUFSIZE); // 复制当前的path
strcat(pathbuf, "/.*"); // 追加后缀
if (glob(pathbuf, GLOB_APPEND, NULL, &globres)) // glob出错,这里的glob调用就用到了第二个参数flags里的追加
{
fprintf(stderr, "glob() failed!\n");
globfree(&globres);
exit(1);
}
sum = 0;
for (i = 0; i < globres.gl_pathc; ++i)
{
if (path_noloop(globres.gl_pathv[i])) // 只有不是.或者..才传入
{
sum += mydu(globres.gl_pathv[i]);
}
}
sum += statres.st_blocks; // 还要加上目录本身的大小
globfree(&globres);
return sum;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "please use right argc!\n");
exit(1);
}
printf("%lld\n", mydu(argv[1]) / 2);
exit(0);
}
运行结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ du ../iostudy/
176 ../iostudy/
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./mydu ../iostudy/
176
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ du ~/task1
8 /home/liugenyi/task1/.vscode
36 /home/liugenyi/task1/test1
132 /home/liugenyi/task1/test2
180 /home/liugenyi/task1
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./mydu ~/task1
180
但是其实有些目录可能会不一致,为什么呢?权限问题。
其实这段代码还可以进行一些优化,可以把没有跨越递归的变量设置为static,优化如下:
//这个变量是没有跨越递归的,因此可以定义成static
static char pathbuf[PATHBUFSIZE];
//---------------------------
struct stat statres;
glob_t globres;
int i;
long long sum;
其实statres也可以让它不跨越递归,从而用static修饰:
sum = statres.st_blocks; // 还要加上目录本身的大小(提到前面来,这样它也不跨越递归)
for (i = 0; i < globres.gl_pathc; ++i)
{
if (path_noloop(globres.gl_pathv[i])) // 只有不是.或者..才传入
{
sum += mydu(globres.gl_pathv[i]);
}
}
//sum += statres.st_blocks; // 还要加上目录本身的大小(这里是跨越递归的)
globfree(&globres);
二、系统数据文件和信息
1、用户信息查询相关/etc/passwd
用户信息相关的东西一般存放在/etc/passwd中,但是根据不同环境可能不一样,因此我们不要采取通过文件获得信息,使用函数最为合适,这里介绍两个函数:
#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwnam(const char *name);//通过名字返回用户信息
struct passwd *getpwuid(uid_t uid);//通过uid返回用户信息
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 */
};
这里使用getpwuid来实现个通过uid来查询用户名:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
int main(int argc, char **argv)
{
struct passwd *passwdres;
if (argc != 2)
{
fprintf(stderr, "please use rigth argc!\n");
exit(1);
}
passwdres = getpwuid(atoi(argv[1]));
puts(passwdres->pw_name);
exit(0);
}
结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./getpwuid 1000
liugenyi
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./getpwuid 0
root
2、组信息查询相关/etc/group
组信息相关的东西一般存放在/etc/group中,但是根据不同环境可能不一样,因此我们不要采取通过文件获得信息,使用函数最为合适,这里介绍两个函数:
#include <sys/types.h>
#include <grp.h>
struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);
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、时间戳
下面介绍几个与时间戳相关的函数:
#include <time.h>
time_t time(time_t *tloc);//returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).
//这个time有两种用法:用一个指针去接收time(NULL),或者把它传进去当作传入传出参数time(指针)
struct tm *localtime(const time_t *timep);//把time_t类型的时间转换成结构体tm类型的时间(更方便人类理解的时间)
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);//按你指定的格式把时间写入s中,s的最大长度为max
//format参数详细见man手册
time_t mktime(struct tm *tm);//把结构体tm类型的时间转换成time_t类型的时间
//注意这里的参数没有加const修饰,也就是说它可能会改你传入的参数,这其实也是它的一个副作用,在类型转换前它会先检查你的格式是否正确等,若有问题他会自动帮你调整
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 */
};
这里我们实现一个小例子来使用一下这些函数,顺便复习一下我们第一节的io操作。
我们每秒往一个指定文件里面写入一个时间,实现如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define FILENAME "./timelog"
#define BUFSIZE 1024
int main()
{
FILE *fd;
int count = 0; // 计数
char buf[BUFSIZE];
time_t stamp;
struct tm *tmres;
fd = fopen(FILENAME, "a+");
if (fd == NULL) // 文件打开失败
{
fprintf(stderr, "fopen()\n");
exit(1);
}
while (fgets(buf, BUFSIZE, fd) != NULL) // 读
{
count++;
}
while (1) // 写
{
time(&stamp);//也可以写成stamp = time(NULL);
tmres = localtime(&stamp);
fprintf(fd, "%-4d%d-%d-%d %d:%d:%d\n", ++count, tmres->tm_year + 1900, tmres->tm_mon + 1, \
tmres->tm_mday, tmres->tm_hour, tmres->tm_min, tmres->tm_sec);
fflush(fd); // 注意第一节讲过,这里属于全缓冲,\n不会刷buf,只能等buf满了自己刷到文件,因此我们要是想要看到一秒一次的写文件只能用fflush手动刷
sleep(1);
}
fclose(fd); // 其实程序根本走不到这里,因为我们上面是死循环,只能通过ctrl+c终止程序,或者后面讲到进程线程的控制的时候再用其他方式来完善
exit(0);
}
结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ tail -f ./timelog
18 2024-3-12 14:43:7
19 2024-3-12 14:43:8
20 2024-3-12 14:43:9
21 2024-3-12 14:43:10
22 2024-3-12 14:43:11
23 2024-3-12 14:43:12
24 2024-3-12 14:43:13
25 2024-3-12 14:43:14
26 2024-3-12 14:43:15
27 2024-3-12 14:43:16
28 2024-3-12 14:43:17
29 2024-3-12 14:43:18
30 2024-3-12 14:43:19
31 2024-3-12 14:43:20
32 2024-3-12 14:43:21
我们再来一个功能把其他几个时间相关函数也用用,实现计算100天后的时间戳:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define TIMEBUFSIZE 1024
int main()
{
time_t stamp;
struct tm *tmres;
char timebuf[TIMEBUFSIZE];
// 首先获取当前时间
stamp = time(NULL);
tmres = localtime(&stamp);
strftime(timebuf, TIMEBUFSIZE, "NowTime:%Y-%m-%d", tmres);
puts(timebuf);
// 加上一百天后,这里的思路第一想法是把stamp加上100天转换的秒数,然后再依次转tmres。可以,但是繁琐
// 上面我们看mktime的原型的时候有说过它的副作用,我们这里刚好可以使用它的副作用
tmres->tm_mday += 100;
(void)mktime(tmres); // 我不需要它的返回值,我不用它作为类型转换,我只用它的副作用(判断我们传入的tmres后溢出,帮我们调整tmres)
strftime(timebuf, TIMEBUFSIZE, "100days later:%Y-%m-%d", tmres);
puts(timebuf);
exit(0);
}
运行结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./100days
NowTime:2024-03-12
100days later:2024-06-20
三、进程环境
1、main函数
int main(int argc, char *argv[]);//大家再熟悉不过了,值得一提的是以前的main函数其实有三个参数,最后一个是环境变量,不过现在用不到了
2、进程的终止
正常终止:
1、从main函数返回
2、调用exit
3、调用_exit或者Exit
4、最后一个线程从其启动例程返回
5、最后一个线程调用pthread_exit
异常终止:
1、调用abort
2、接到一个信号并终止
3、最后一个线程对其取消请求做出相应
正常终止的4、5和异常终止的1、2、3后面讲到多线程那些再提,这里我想说说exit、atexit、_exit
exit、atexit、_exit
#include <stdlib.h>
void exit(int status);//causes normal process termination and the value of status & 0xFF is returned to the parent
#include <stdlib.h>
int atexit(void (*function)(void));//进程正常结束的时候,倒序调用钩子函数
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);
这里写个简单的例子来看看atexit的运行过程:
#include <stdio.h>
#include <stdlib.h>
static void f1(void)
{
puts("f1()------\n");
}
static void f2(void)
{
puts("f2()------\n");
}
static void f3(void)
{
puts("f3()------\n");
}
int main()
{
puts("begin-----------\n");
atexit(f1);
atexit(f2);
atexit(f3);
puts("end-----------\n");
exit(0);
}
看看运行结果:可以发现执行到atexit不会马上调用,会在进程正常终止的时候调用,并且是倒序调用
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./gouzi
begin-----------
end-----------
f3()------
f2()------
f1()------
atexit的用处很像C++里的析构函数,我们在之前的代码里面是不是写到过这种情况:(这儿为了方便我就用伪代码)
fd1 = fopen(...);
if(fd1 == NULL)//检查fd1是否打开成功
{
.....
}
fd2 = fopen(...);
if(fd2 == NULL)//检查fd2是否打开成功
{
fclose(fd1);//这里我们就考虑到了,如果fd1成功打开但是fd2打开失败,我们要释放fd1的资源
fprintf(...);
exit(1);
}
那么问题来了,要是我打开了100个fd呢,我是不是在fd100的判断里会关闭fd1-fd99呢,代码会非常冗余,因此我们可以使用atexit,伪代码如下:
fd1 = fopen(...);
if(fd1 == NULL)//检查fd1是否打开成功
{
.....
}
atexit(这里面去closefd1);
fd2 = fopen(...);
if(fd2 == NULL)//检查fd2是否打开成功
{
fprintf(...);
exit(1);//它调用之前会调用atexit(这里面去closefd1);
}
atexit(这里面去closefd2);
......
......
这样的话,如果fd2打开失败了,exit(1)执行之前,就会倒序调用钩子函数,就会调用到closefd1。
_exit
和_Exit
看上去和exit
差不多,但是其实有区别的。
首先它们都是用于结束进程的两个 Unix 系统调用,它们的主要区别在于 exit
执行后会立即返回给内核,而 exit
会先进行一些清理操作,如关闭文件描述符、清理内存以及执行其他内核清理函数,然后再将控制权交给内核。因此我们也可以推断出,_exit
执行后是不会执行钩子函数的。那么我们看下面这个例子:
int func()
{
...
return 0或者1或者2;
}
int main()
{
.....
int res = func();
switch(res)
{
case 0:...
case 1:...
case 2:...
default:
exit(1);//我们是不是觉得,要是万一func返回给我了其他的值,我退出就是
//但这样就会出现大问题,很有可能是main函数里前面的执行出错,例如读写内存越界覆盖了res的值等等
//这种问题就比较严重,如果使用exit退出,那么它会刷新io,刷新文件等等,就可能将问题扩散
//因此使用_exit直接结束最好,或者用abort()结束
}
exit(0);
}
3、命令行参数的分析
getopt
#include <unistd.h>
int getopt(int argc, char * const argv[], const char *optstring);
//argc:命令行参数个数
//argv:命令行参数内容
//optstirng:包含的选项串
extern char *optarg;//如果你optstring某个选项加了“:”,那这个指针就会指向后面修饰的那个串
extern int optind, opterr, optopt;
//optind:表示读到了当前哪个参数的下标
//opterr:和errno差不多
现在我们在上面时间函数例子的基础上拓展功能,实现我可以通过命令行传参数的方式控制不同的输出内容。
要求:./mydate -y表示只输出年份;-h表示只输出时间;-h 24表示按照24小时制输出;-h 12表示按照12小时制输出;-y 2表示输出两位的年份;-y 4表示输出四位的年份;/../..表示将内容输出到文件里不显示在屏幕上。
/*
-y:year
-m:month
-d:day
-H:hour
-M:minute
-S:second
/../../:将时间输出到文件,不显示到终端
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#define TIMEBUFSIZE 1024
#define FMTSTRSIZE 1024
int main(int argc, char **argv)
{
time_t stamp;
struct tm *tmres;
char timebuf[TIMEBUFSIZE];
int c;
FILE *fp = stdout;
char fmtstr[FMTSTRSIZE]; // 最终模式串
fmtstr[0] = '\0';
// 获取当前时间
stamp = time(NULL);
tmres = localtime(&stamp);
while (1)
{
c = getopt(argc, argv, "-H:MSy:md"); //: 表示某选项的后面会跟参数 -表示有非选项的传参,比如 ls -a /tmp/out这个/tmp/out就是非选项的传参,如果遇到了返回1
if (c < 0)
{
break;
}
switch (c)
{
case 1:
fp = fopen(argv[optind - 1], "w"); // optind是当前参数的下标,刚刚getopt返回为1,但是optind已经指向了下一个位置了,因此我们要-1回到非选项的传参的位置
if (fp == NULL)
{
perror("fopen()");
fp = stdout;
}
break;
case 'H':
if (strcmp(optarg, "12") == 0) // 12进制的小时表示
{
strncat(fmtstr, "%I(%P) ", FMTSTRSIZE);
}
else if (strcmp(optarg, "24") == 0) // 24进制的小时表示
{
strncat(fmtstr, "%H ", FMTSTRSIZE);
}
else
{
fprintf(stderr, "invalid arguments of H!\n"); // 报错参数输入有问题
}
break;
case 'M':
strncat(fmtstr, "%M ", FMTSTRSIZE);
break;
case 'S':
strncat(fmtstr, "%S ", FMTSTRSIZE);
break;
case 'y':
if (strcmp(optarg, "2") == 0) // 2位年份
{
strncat(fmtstr, "%y ", FMTSTRSIZE);
}
else if (strcmp(optarg, "4") == 0) // 4位年份
{
strncat(fmtstr, "%Y ", FMTSTRSIZE);
}
else
{
fprintf(stderr, "invalid arguments of y!\n"); // 报错参数输入有问题
}
break;
case 'm':
strncat(fmtstr, "%m ", FMTSTRSIZE);
break;
case 'd':
strncat(fmtstr, "%d ", FMTSTRSIZE);
break;
default:
break;
}
}
strncat(fmtstr, "\n", FMTSTRSIZE); // 追加个换行
strftime(timebuf, TIMEBUFSIZE, fmtstr, tmres);
fputs(timebuf, fp);
if (fp != stdout)
{
fclose(fp); // 记得确认不是标准输出再关闭资源
}
exit(0);
}
运行结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./mydate -ymHMS
invalid arguments of y!
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./mydate -mMSH 24
03 00 47 21
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./mydate -y 4 -m -d -H 24 -M -S
2024 03 12 21 00 54
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./mydate -y 4 -m -d -H 24 -M -S ./timetest
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ cat ./timetest
2024 03 12 21 01 00
其实这个程序还可以继续优化,比如我传入多个非选项传参,是每个文件都执行写入,还是只管最先出现的文件,这里就不再实现。可以说说思路,如果只想执行最先出现的文件,在case 1进入后加个if判断fp是否为stdout,如果是,再进入后面的操作,这样的话传入多个文件也只会只执行一次最先出现的文件。
4、环境变量
可以理解为key=value
用export命令可以查看
并且environ类似char *argv[]结构,因此我们可以使用它来打印出环境变量的内容:
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main()
{
for (int i = 0; environ[i] != NULL; ++i)
{
puts(environ[i]);
}
exit(0);
}
运行结果和命令行输入export一致
这里有三个和环境变量有关的函数掌握一下:
#include <stdlib.h>
char *getenv(const char *name);//通过名字获取值
int setenv(const char *name, const char *value, int overwrite);//新增或者修改
//name:属性名,没有的话新增,有的话不变或者修改(和overwrite的值有关)
//value:属性值
//overwrite:属性名存在的话选择是否覆盖
int unsetenv(const char *name);//删除一个环境变量
int putenv(char *string);//和setenv的功能一样,但是要求传入的串满足:name=value
//不好用,没有setenv好用,注意传入的值没有用const修饰,setenv有const修饰
写个简单的例子试试:
#include <stdio.h>
#include <stdlib.h>
int main()
{
puts(getenv("PATH")); // 这就很类似javaweb里读xml配置文件
exit(0);
}
结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./getenv
/home/liugenyi/.vscodeserver/bin/019f4d1419fbc8219a181fab7892ebccf7ee29a2/bin/remotecli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
5、库
动态库
静态库
手工装载库
动态库、静态库c基础就不再多提,这里简单介绍手工装载库实现:
#include <dlfcn.h>
void *dlopen(const char *filename, int flags);//指定要装载的库以及装载模式
int dlclose(void *handle);//关闭
char *dlerror(void);//其实就是把strerror封装了一层
void *dlsym(void *handle, const char *symbol);//在一个成功打开的动态库(handle)中查找某个符号(symbol),如果能找到,返回入口地址
例子:手动装载math库,并且查找cos
#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); // RTLD_LAZY方式:推迟打开,用的时候再打开
if (!handle)
{
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror();
cosine = (double (*)(double))dlsym(handle, "cos"); // 查找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);
}
6、函数跳转
关于跳转的函数如下:
goto;//这是个标志不是函数,goto不能安全跨函数跳转
#include <setjmp.h>
int setjmp(jmp_buf env);//设置跳转点
void longjmp(jmp_buf env, int val);//携带值(val)跳转到跳转点(env)
首先我们来看一下一个正常执行的函数调用:
#include <stdio.h>
#include <stdlib.h>
static void d(void)
{
printf("%s():begin.\n", __FUNCTION__);
printf("%s():end.\n", __FUNCTION__);
}
static 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__);
}
static 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__);
}
static void a(void)
{
printf("%s():begin.\n", __FUNCTION__);
printf("%s():call b().\n", __FUNCTION__);
b();
printf("%s():b() returned.\n", __FUNCTION__);
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);
}
运行结果:符合函数调用规则
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./fjmp
main():begin.
main():call a().
a():begin.
a():call b().
b():begin.
b():call c().
c():begin.
c():call d().
d():begin.
d():end.
c():d() returned.
c():end.
b():c() returned.
b():end.
a():b() returned.
a():end.
main():a() returned.
main():end.
下面我们设置跳转点:
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
jmp_buf save;
static void d(void)
{
printf("%s():begin.\n", __FUNCTION__);
printf("%s():come ! jump now!\n", __FUNCTION__);
longjmp(save, 6);
printf("%s():end.\n", __FUNCTION__);
}
static 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__);
}
static 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__);
}
static void a(void)
{
int ret;
printf("%s():begin.\n", __FUNCTION__);
ret = setjmp(save);
if (ret == 0)
{
printf("%s():call b().\n", __FUNCTION__);
b();
printf("%s():b() returned.\n", __FUNCTION__);
}
else // 从别处跳转过来的
{
printf("%s():jump back with %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);
}
结果:
liugenyi@liugenyi-virtual-machine:~/linuxsty/fs$ ./fjmp
main():begin.
main():call a().
a():begin.
a():call b().
b():begin.
b():call c().
c():begin.
c():call d().
d():begin.
d():come ! jump now!
a():jump back with 6.
a():end.
main():a() returned.
main():end.
可以看到d跳转直接去了a,实现了跨函数安全跳转,并且就算d携带过去的跳转值写0,它会自动帮你转成1,防止你故意玩骚的。
7、资源的获取及控制
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);//获得资源(resource)放到rlim
int setrlimit(int resource, const struct rlimit *rlim);//把rlim值设置到资源(resource)中
struct rlimit {
rlim_t rlim_cur; /* Soft limit */
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};
//普通用户只能提高或者降低rlim_cur;或者降低rlim_max但不能提高rlim_max
//root用户可以提高或降低rlim_max/rlim_cur
//rlim_cur即使升高也不能超过rlim_max