Linux系统编程第二部分:文件系统

主要内容如下:

一、目录和文件

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

  • 41
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值