• linux系统编程(六) linux文件系统的操作


    一、文件系统操作

    1.文件存储

    首先了解如下文件存储相关概念:inode、 dentry、 数据存储、文件系统。

    1.1 inode

    其本质为结构体,存储文件的属性信息。如:权限、类型、大小、时间、用户、盘块位置……也叫作文件属性管理结构,大多数的inode都存储在磁盘上。
    少量常用、近期使用的inode会被缓存到内存中。

    1.2 dentry

    目录项,其本质依然是结构体,重要成员变量有两个 {文件名,inode,…},而文件内容(data)保存在磁盘盘块中。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    dentry被删掉之后,inode还在,磁盘对应位置就不能被替换。说明文件并没有真正删除。
    但是inode被删除以后,对应磁盘允许被替换,这样就会导致,文件真正可能被删除。

    1.3 文件系统

    文件系统是,一组规则,规定对文件的存储及读取的一般方法。文件系统在磁盘格式化过程中指定。 常见的文件系统有:fat32 ntfs exfat ext2 、ext3 、ext4

    2. 文件操作

    2.1 stat函数

    获取文件属性,(从inode结构体中获取)		
    	int stat(const char *path, struct stat *buf); 	成返回0;失败返回-1 设置errno为恰当值。
    		参数1:文件名
    		参数2:inode结构体指针 (传出参数)
    
    文件属性将通过传出参数返回给调用者。
    练习:使用stat函数查看文件属性												
    【stat.c】
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    The stat structure

     
           All of these system calls return a stat structure, which contains the following fields:
    
               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
               };
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc,char *argv[])
    {
            struct stat sbuf;
    
            int ret =stat(argv[1],&sbuf);
            if(ret == -1){
                    perror("stat error");
                    exit(1);
    
            }
    
            printf("file size :%ld\n",sbuf.st_size);
    
            return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.2 lstat函数

    int lstat(const char *path, struct stat *buf); 
    成返回0;
    失败返回-1 设置errno为恰当值。
    
    练习:给定文件名,判断文件类型。											【get_file_type.c】
    
    	文件类型判断方法:st_mode 取高4位。 但应使用宏函数:           
    	   S_ISREG(m)  	is it a regular file?
           S_ISDIR(m)  	directory?
           S_ISCHR(m)  	character device?
           S_ISBLK(m)  	block device?
           S_ISFIFO(m) 	FIFO (named pipe)?
           S_ISLNK(m)  	symbolic link?  (Not in POSIX.1-1996.)
           S_ISSOCK(m) 	socket?  (Not in POSIX.1-1996.)
    穿透符号链接:stat:会;lstat:不会
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc,char *argv[])
    {
            struct stat sbuf;
    
            int ret =lstat(argv[1],&sbuf);
            if(ret == -1){
                    perror("stat error");
                    exit(1);
    
            }
    
            if(S_ISREG(sbuf.st_mode)){
                    printf("it's a regular\n");
            }else if(S_ISDIR(sbuf.st_mode)){
                    printf("it's a dir\n");
            }else if(S_ISFIFO(sbuf.st_mode)){
                    printf("it's a pipe");
            }else if(S_ISLNK(sbuf.st_mode)){
                    printf("it's a sym link\n");
            }
    
            return 0;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    2.3 特殊权限位

    在这里插入图片描述

    包含三个二进制位。依次是:设置组ID位setGID;设置用户ID位setID;黏住位sticky

    2.3.1 黏住位
    早起计算机内存紧,只有精要的常用的程序可以常驻物理内存,剩下的要暂存磁盘中。当内存不够用的时候会将该部分程序存回磁盘,腾出内存空间。若文件设置了黏住位,那么即使在内存比较吃紧的情况下,也不会将该文件回存到磁盘上。由于现阶段操作系统的虚拟内存管理分页算法完善。该功能已经被废弃。
    但我们仍然可以对目录设置黏住位。被设置了该位的目录,其内部文件只有:
    ①超级管理员
    ②该目录所有者
    ③该文件的所有者 
    以上三种用户有权限做删除、修改操作。其他用户可以读、创建但不能随意删除。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2.3.1 setUID位
    进程有两个ID:EID(有效用户ID),表示进程履行哪个用户的权限。
    			      UID(实际用户ID),表示进程实际属于哪个用户。
    
    	多数情况下,EID和UID相同。但是,当文件的setID被设置后两个ID则有可能不一样。
    	例如:当进程执行一个root用户的文件,若该文件的setID位被设置为1, 那么执行该文件时,进程的UID不变。EID变为root,表示进程开始履行root用户权限。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    setGID位于setID相类似。

    2.4 access函数

    测试指定文件是否存在/拥有某种权限。
    	int access(const char *pathname,  int mode); 
    
    成功/具备该权限:0;
    失败/不具备 -1 设置errno为相应值。
    		参数2:R_OK、W_OK、X_OK
    	通常使用access函数来测试某个文件是否存在。F_OK	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.5 chmod函数

    修改文件的访问权限
    int chmod(const char *path, mode_t mode);		
    成功:0;
    失败:-1 设置errno为相应值
    int fchmod(int fd, mode_t mode);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.6 truncate函数

    截断文件长度成指定长度。常用来拓展文件大小,代替lseek。
           int truncate(const char *path, off_t length);	
           成功:0;
           失败:-1 设置errno为相应值
           int ftruncate(int fd, off_t length);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.7 link函数

    思考,为什么目录项要游离于inode之外,画蛇添足般的将文件名单独存储呢??这样的存储方式有什么样的好处呢?
    	其目的是为了实现文件共享。Linux允许多个目录项共享一个inode,即共享盘块(data)。不同文件名,在人类眼中将它理解成两个文件,但是在内核眼里是同一个文件。
    link函数,可以为已经存在的文件创建目录项(硬链接)int link(const char *oldpath,  const char *newpath);	成功:0;失败:-1设置errno为相应值
    	注意:由于两个参数可以使用“相对/绝对路径+文件名”的方式来指定,所以易出错。
    		如:link("../abc/a.c", "../ioc/b.c")若a.c,b.c都对, 但abc,ioc目录不存在也会失败。
    	mv命令既是修改了目录项,而并不修改文件本身。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc,char *argv[])
    {
            link(argv[1],argv[2]);
    
            unlink(argv[1]);
    
    
            return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.8 unlink函数

    删除一个文件的目录项;
    	int unlink(const char *pathname);	成功:0;失败:-1设置errno为相应值
    练习:编程实现mv命令的改名操作												【imp_mv.c】
    	注意Linux下删除文件的机制:不断将st_nlink -1,直至减到0为止。无目录项对应的文件,将会被操作系统择机释放。(具体时间由系统内部调度算法决定)
    	因此,我们删除文件,从某种意义上说,只是让文件具备了被释放的条件。
    unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放。要等到所有打开该文件的进程关闭该文件,系统才会挑时间将该文件释放掉。				【unlink_exe.c】
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    /*
     * unlink函数是删除一个dentry
     */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(void)
    {
            int fd,ret;
            char *p = "test of unlink\n";
            char *p2 = "after write something.\n";
    
            fd = open("temp.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
            if(fd<0){
                    perror("open temp error");
                    exit(1);
            }
    
    
            ret = unlink("temp.txt");
            if(ret<0){
                    perror("unlink error");
                    exit(1);
            }
            ret = write(fd,p,strlen(p));
            if(ret == -1){
                    perror("----write error");
            }
            printf("hi!I'm printf\n");
            ret = write(fd,p2,strlen(p2));
            if (ret == -1){
                    perror("----write error");
            }
    
            printf("Enter anykey continue\n");
            getchar();
            close(fd);
    
    
            return 0;
    }
    ~                                                                                                  
    ~    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    2.9 隐式回收

    当进程结束运行时,所有该进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。

    2.10 symlink函数

    创建一个符号链接
    int symlink(const char *oldpath, const char *newpath);	成功:0;失败:-1设置errno为相应值
    
    • 1
    • 2

    2.11 readlink函数

    读取符号链接文件本身内容,得到链接所指向的文件名。
    ssize_t readlink(const char *path, char *buf, size_t bufsiz);	成功:返回实际读到的字节数;
    失败:-1
    设置errno为相应值。
    
    • 1
    • 2
    • 3
    • 4

    2.12 rename函数

    重命名一个文件。
     int rename(const char *oldpath, const char *newpath); 成功:0;失败:-1设置errno为相应值
    
    • 1
    • 2

    3.目录操作

    工作目录:“./”代表当前目录,指的是进程当前的工作目录,默认是进程所执行的程序所在的目录位置。

    3.1 getcwd函数

    获取进程当前工作目录	(3,标库函数)
    		char *getcwd(char *buf, size_t size);	
    		成功:buf中保存当前进程工作目录位置。
    		失败返回NULL
    • 1
    • 2
    • 3
    • 4

    3.2 chdir函数

    改变当前进程的工作目录
    		int chdir(const char *path); 	成功:0;失败:-1设置errno为相应值
    
    	练习:获取及修改当前进程的工作目录,并打印至屏幕。									【imp_cd.c】
    
    • 1
    • 2
    • 3
    • 4

    3.3 文件、目录权限

    注意:目录文件也是“文件”。其文件内容是该目录下所有子文件的目录项dentry。 可以尝试用vim打开一个目录。

    在这里插入图片描述

    目录设置黏住位:若有w权限,创建不变,删除、修改只能由root、目录所有者、文件所有者操作。

    3.4 opendir函数

    根据传入的目录名打开一个目录 (库函数)			DIR * 类似于 FILE *
    	DIR *opendir(const char *name);	  成功返回指向该目录结构体指针,失败返回NULL	
        参数支持相对路径、绝对路径两种方式:
        例如:打开当前目录:
        ① getcwd() , opendir()opendir(".");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.5 closedir函数

    关闭打开的目录
    int closedir(DIR *dirp);		成功:0;失败:-1设置errno为相应值
    
    • 1
    • 2

    3.6 readdir函数

    读取目录	(库函数)
    	struct dirent *readdir(DIR *dirp);  成功返回目录项结构体指针;失败返回NULL设置errno为相应值
    		需注意返回值,读取数据结束时也返回NULL值,所以应借助errno进一步加以区分。
    	struct 结构体:
               struct dirent {
                   ino_t          d_ino;      inode编号
                   off_t          d_off;       
                   unsigned short  d_reclen;    文件名有效长度
                   unsigned char   d_type;     类型(vim打开看到的类似@*/)
                   char          d_name[256];文件名
               };
    	其成员变量重点记忆两个:d_ino、d_name。实际应用中只使用到d_name。
    
    	练习1:实现简单的ls功能。												
    	练习2:实现ls不打印隐藏文件。每5个文件换一个行显示。							
    
    	拓展1:实现ls -a -l 功能。
    拓展2:统计目录及其子目录中的普通文件的个数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(int argc,char *argv[])
    {
            DIR * dp;
            struct dirent * sdp;
            dp = opendir(argv[1]);
            if(dp==NULL){
                    perror("opendir error");
                    exit(1);
            }
    
    
            while((sdp=readdir(dp))!=NULL){
                    if((strcmp(sdp->d_name,".")==0))
                            continue;
    
                    printf("%s\t",sdp->d_name);
            }
            printf("\n");
    
            closedir(dp);
            return 0;
    }
    ~                                                                                                  
    ~                                                                                                  
    ~                                                                                                  
    ~                                                                                                  
    ~                                                                                                  
    ~           
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    3.7 rewinddir函数

    回卷目录读写位置至起始。
    void rewinddir(DIR *dirp);	返回值:无。
    
    • 1
    • 2

    3.8 telldir/seekdir函数

    获取目录读写位置
    		long telldir(DIR *dirp); 成功:与dirp相关的目录当前读写位置。失败-1,设置errno
    	修改目录读写位置
    		void seekdir(DIR *dirp, long loc); 返回值:无
    		参数loc一般由telldir函数的返回值来决定。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.递归遍历目录

    查询指定目录,递归列出目录中文件,同时显示文件大小。								【ls_R.c】
    
    • 1
    #include 
    void isfile(char *name);
    
    void read_dir(char *dir,void (*func)(char *))
    {
            char path[256];
            DIR * dp;
            struct dirent *sdp;
    
            dp = opendir(dir);
            if(dp == NULL){
                    perror("opendir error");
                    return;
            }
    
            while((sdp = readdir(dp))!= NULL){
                    if(strcmp(sdp->d_name,".")==0 || strcmp(sdp->d_name,"..")==0){
                            continue;
                    }
                    sprintf(path,"%s/%s",dir,sdp->d_name);
                    (*func)(path);  
            }
            closedir(dp);
            return ;
    }
    
    
    void isfile(char *name)
    {
            int ret ;
            struct stat sb;
            ret = stat(name,&sb);
            if(ret == -1){
                    perror("stat error");
                    return;
            }
    
            if(S_ISDIR(sb.st_mode)){
                    read_dir(name,isfile);
            }
            else{
            printf("%20s\t%ld\n",name,sb.st_size);
            }
    
            return;
          
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    5.重定向

    dup 和 dup2函数
    	int dup(int oldfd); 
    	成功:返回一个新文件描述符;
    	失败:-1设置errno为相应值
           	int dup2(int oldfd, int newfd); 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    在这里插入图片描述

    重定向示
    记忆方法两种:
    1. 文件描述符的本质角度理解记忆。
    2. 从函数原型及使用角度,反向记忆。
    练习:借助dup函数编写mycat程序,实现cat file1 > file2 命令相似功能。						【mycat.c】
    
    • 1
    • 2
    • 3
    • 4
    • 5
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc,char *argv[])
    {
            int fd = open(argv[1],O_RDONLY);
            int newfd = dup(fd);
            printf("newfd = %d\n",newfd);
    
            return 0;
    }
    
    ~                                                                                                  
    ~            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc,char *argv[])
    {
            int fd1 = open(argv[1],O_RDWR);
            int fd2 = open(argv[2],O_RDWR);
    
            int fdret = dup2(fd1,fd2);
            printf("fdret = %d\n",fdret);
    
            int ret =write(fd2,"1234567",7);
            printf("ret = %d\n",ret);
    
            dup2(fd1,STDOUT_FILENO);
    
            printf("-------------------------886");
            return 0;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc,char *argv[])
    {
            int fd1 = open(argv[1],O_RDWR);
    
            printf("fd1 = %d\n",fd1);
    
            int newfd = fcntl(fd1,F_DUPFD,0);
            printf("newfd = %d\n",newfd);
    
            int newfd2 = fcntl(fd1,F_DUPFD,7);
            printf("newfd2 = %d\n",newfd2);
    
            int ret = write(newfd2,"YYYYYY",7);
            printf("ret = %d\n",ret);
    
    
            return 0;
    }
    ~                                                                                                  
    ~                                                                                                  
    ~                                                                                                  
    ~            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    从0开发属于自己的nestjs框架的mini 版 —— ioc篇
    向上生长笔记
    Python - 函数|异常|模块|包|类和对象|封装|继承
    KingbbaseES V8R6集群维护案例之---集群之间数据迁移
    learnOpenGl
    【我的世界Minecraft-MC】常见及各种指令大杂烩【2022.8版】
    vue解决报错Unable to preventDefault inside passive event listener invocation.
    分布式事务-TCC异常-空回滚
    aspnetcore插件开发dll热加载
    伊理威科技:抖音商家入驻怎么样
  • 原文地址:https://blog.csdn.net/weixin_45840087/article/details/126739555