JackCin's blog JackCin's blog
首页
  • 页面

    • Html
    • CSS
  • 核心

    • JavaScript基础
    • JavaScript高级
  • 框架

    • Vue
  • jQuery
  • Node
  • Ajax
Linux
  • 操作系统
  • 数据结构与算法
  • 51单片机
  • CC2530
  • 网站
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

JackCin

前端小菜鸡(✪ω✪)
首页
  • 页面

    • Html
    • CSS
  • 核心

    • JavaScript基础
    • JavaScript高级
  • 框架

    • Vue
  • jQuery
  • Node
  • Ajax
Linux
  • 操作系统
  • 数据结构与算法
  • 51单片机
  • CC2530
  • 网站
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Linux系统编程入门

    • Linux开发工具
    • 文件IO
    • Linux 多进程开发

    • Linux 多线程开发

    • Linux 网络编程

    • Linux
    • Linux系统编程入门
    JackCin
    2023-09-12
    目录

    文件IO

    # 文件 I/O

    • 我们的IO是站在内存的角度去看待的, 输入( I )就是从文件里面把数据读取到内存,输出( O )就是从内存里面把数据写入到文件

    # 1、标准C库 IO函数

    O函数

    • 标准c库的 IO 函数有跨平台性,即我们用标准c库开发的程序再个个平台(操作系统)都可以运行

    • 标准c库AKA第三方库,它是不属于操作系统的,它之所以能实现跨平台是因为他在不同平台会调用不同平台的系统 api

    • 缓冲区大小默认是 8k (可调节,但不建议)

    # 2、标准c库IO和Linux系统IO的关系

    标准c库-IO和-Linux系统IO的关系

    # 3、虚拟地址空间

    虚拟地址空间

    • 可执行程序运行期间就会对应一个虚拟内存空间,程序运行结束空间就不存在了
    • 进程是系统给程序分配资源的最小单位
    • 32位的计算机虚拟地址空间大小就是2^32(4G),我们上图就S是32位的
      • 64位电脑并不是 2^64 位,而是 2^48
    • linux kernel(Linux 内核区) 普通用户没有权限操作,要想操作内核区必须调用系统的API,通过系统 API函数(如 read ,write)来进行操作

    虚拟地址空间

    • 全局变量:保存在内存的全局存储区中,占用静态的存储单元
    • 局部变量:保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元

    # 4、文件描述符

    文件描述符

    • 文件描述符是保存在内核区,由内核进行管理
      • 内核有内存管理的功能,在内核的内存管理模块里有PCB进程控制块,由它来管理文件描述符
      • PCB 是一个非常复杂的结构体,在这个结构体里有一个数组(文件描述符表),之所以需要这个表是因为我们可能会同时打开多个文件,那就会有多个文件描述符
        • 文件描述符表的大小默认是 1024
      • 文件描述符表的前三个默认是被占用的(如上图),标准输入(0),标准输出(1),标准错误(2),他们指向的是当前的终端
      • 注意: “每打开...描述符” 这段话的意思是,好比我们有3个文件描述符,那就是从3开始占用文件描述符表的空间( 即3,4,5 ),当3号空间的文件描述符被释放,那重新产生的文件描述符就是进到3里,而不是存放到6里

    # 5、Linux 系统 IO 函数

    # 5.1 open打开文件

    • int open(const char *pathname, int flags);
    • 使用 man 2 open 查看 open函数
    /*
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <fcntl.h>
    
        //打开一个已经存在的文件
        int open(const char *pathname, int flags);
            参数:
                - pathname : 要打开的文件路径
                - flags: 对文件的操作权限设置还有其他的设置
                O_RDONLY(可读), O_WRONLY(可写), O_RDWR(可读可写)  这3个是互斥的
    
            返回值:返回一个新的文件描述符,如果调用失败,返回 -1
    
        errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。
    
        #include <stdio.h>
        void perror(const char *s); 作用:打印error对应的错误描述
            s参数:用户描述,比如hello,最终输出的内容是 hello:xxx(实际的错误描述)
    
    */
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // open.c ,此时我们当前文件夹里没有a.txt文件
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>   //perror
    #include <unistd.h>  //close
    
    int main()
    {
        //打开一个文件
        int fd = open("a.txt", O_RDONLY);
    
        if (fd == -1)
        {
            perror("open");
        }
    
        // 关闭fd(文件描述符)指向的文件
        //close就是根据文件描述符关闭其指向的文件
        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
    • gcc open.c -o open 生成 可执行文件 open
    • 执行可执行文件 ./open
    //此时我们当前文件夹里没有a.txt文件,那么它就显示没有这样的文件或目录
    open: No such file or directory
    
    1
    2

    # 5.2 open创建新文件

    • int open(const char *pathname, int flags, mode_t mode);
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <fcntl.h>
    
        int open(const char *pathname, int flags, mode_t mode);
            参数:
                - pathname:要创建的文件的路径
                - flags: 对文件的操作权限设置还有其他的设置
                    -必选项: O_RDONLY(可读), O_WRONLY(可写), O_RDWR(可读可写)  这3个是互斥的
                    -可选项:  O_CREAT 文件不存在,创建新文件
                    
                - mode:八进制的数,表示创建出来的新文件的操作权限 比如:0775
                    0777-> rwxrwxrwx , rw-rw-r--  => 0664
                    单独拿一组出来解释一下: rwx -》2^2 + 2^1 + 2^1 = 7
    
                    最终的权限是:mode & ~umask(权限掩码,普通用户的默认的umask是0002,root是0022)
                    比如: 我们给mode 为 0777 那么最终的权限就是:0775
                    ~umask : 0002取反(八进制)那就是 111 111 101 -》0775
                    最终权限:
                            0777  ——>  111111111
                        &   0775  ——>  111111101
                        -----------------------------
                                       111111101
                    按位与: 0和任何数都为0 ,umask的作用就算抹去某些权限
    
    
    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
    • flags 参数是一个int类型的数据,占4个字节,32位。
    • flags 32个位,每一位就是一个标志位。
    • 这也是为什么我们写falgs的参数时用 |[按位或] 的原因,就好比说32位里每一位都代表一个权限,且都为0,然后O_RDONLY是000。。。1,那我们就用 | 将flags的第一位置为1,这里只是打个比方,不是说O_RDONLY就第一位
    // create.c
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    
    int main()
    {   //下面行的权限必须写四位的数,如 0777而不能写 777(这样写的话是10进制数)
        int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
        
        if (fd == -1)
        {
            perror("open");
        }
    
        // 关闭
        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

    open打开新文件

    • 可以看出, create.txt 的权限为 0775
    • 权限掩码 umask:
      • 查看指令: umask
      • 修改指令: umask 想要改成的权限 如 umask 022 ,这种修改方式关闭终端就会被还原
      • 也可以使用 umask()函数在程序中设置这个值
        • man 2 umask,查看umask()函数

    # 5.3 read , write函数

    • ssize_t read(int fd, void *buf, size_t count);
    • ssize_t write(int fd, const void *buf, size_t count);
    
        #include <unistd.h>
        ssize_t read(int fd, void *buf, size_t count);
            参数:
                - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
                - buf:需要读取数据存放的地方,数组的地址 (传出参数)
                - count:指定的数组的大小
            返回值:
                - 成功:
                    >0 :返回实际的读取到得字节数
                    =0 : 文件已经读取完了
                - 失败:-1,并且设置errno
    
        #include <unistd.h>
        ssize_t write(int fd, const void *buf, size_t count);
            参数:
                - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
                - buf:要往磁盘写入的数据,数据
                - count:要写入的数据的实际的大小
            返回值:
                成功:实际写入的字节数
                失败:放回-1,并设置 errno
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    int main()
    {
    
        // 1.通过open打开english.txt文件
        int srcfd = open("english.txt", O_RDONLY);
        if (srcfd == -1)
        {
            perror("open");
            return -1;
        }
    
        // 2.创建一个新的文件 (拷贝文件)
        int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
        if (destfd == -1)
        {
            perror("open");
            return -1;
        }
    
        // 3,频繁的读写操作
        // buf的大小我们可以自己设置
        char buf[1024] = {0};
        // 我们这里设置了1024,那么每次读取应该就是1024,只有最后一次读取不一样可能会小于1024
    
        int len = 0;
        // 得到实际的读取到得字节数,大于0说明还没读完
        while ((len = read(srcfd, buf, sizeof(buf))) > 0)
        {
            write(destfd, buf, len);
        }
    
        // 4.关闭文件
        close(destfd);
        close(srcfd);
    }
    
    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

    read,write函数

    # 5.4、lseek函数

    • off_t lseek(int fd, off_t offset, int whence);
    //标志C库的函数
    #include <stdio.h>
    int fseek(FILE *stream, long offset, int whence);
    //Linux系统函数
    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
        - 参数:
             - fd:文件描述符,通过 open 得到,通过这个 fd操作某个文件
             - offset:偏移量
             - whence:
                SEEK_SET (文件开头)
                    设置文件指针的偏移量
                SEEK_CUR
                    设置偏移量:当前位置 + 第二个参数 offset的值
                SEEK_END (文件的末尾)
                    设置偏移量: 文件大小 + 第二个参数offset的值
        - 返回值: 返回文件指针的位置
    作用:
        1.移动文件指针到文件头
            lseek(fd, 0, SEEK_SET);
        2.获取当前文件指针的位置
            lseek(fd, 0, SEEK_CUR);
        3.获取文件长度
            lseek(fd, 0, SEEK_END);
        4.拓展文件的长度,当前文件10b,110b,增加了 100个字节
            lseek(fd, 100, SEEK_END);
        注意:需要写一次数据
            (拓展的作用:好比我们要下载东西,需要5G,我们可以先拓展出5G的大小,然后再继续下载,这样即使我们继续使用磁盘也不会有影响,因为我们5G的空间已经占用了)
    
    
    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
    //lseek.c
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    
    int main()
    {
        // 打开文件
        int fd = open("hello.txt", O_RDWR);
    
        if (fd == -1)
        {
            perror("open");
        }
    
        // 拓展文件的长度
        int ret = lseek(fd, 100, SEEK_END);
        if (ret == -1)
        {
            perror("lseek");
        }
    
        // 写入一个空数据
        write(fd, " ", 1);
    
        // 关闭文件
        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

    lseek函数

    • 上图第一次编译生成并运行 lseek文件后,hello.txt 文件大小没变化,是因为我们没在拓展文件后写进东西
    • 第二次成功是因为我们在拓展后,写入一个空数据 write(fd, " ", 1);,完整代码看上图
    • 而在扩展出来的这段空间里,每一位都会被 null 填充
    • 注意:lseek()不是扩展数据的功能,只是可以利用lseek()去扩展文件,lseek()只是单纯的移动文件指针偏移。

    # 5.5 stat、lstat函数

    • int stat(const char *pathname, struct stat *statbuf);
    • int lstat(const char *pathname, struct stat *statbuf);
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    int stat(const char *pathname, struct stat *statbuf);
        作用:获取一个文件相关的信息
        参数:
            - pathname: 操作的文件的路径
            - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
        返回值:
            成功:返回0
            失败:返回-1 设置errno
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //stat 结构体
    
    struct stat {
    	dev_t st_dev;  // 文件的设备编号
    	ino_t st_ino;  // 节点
    	mode_t st_mode;  // 文件的类型和存取的权限
    	nlink_t st_nlink;  // 连到该文件的硬连接数目
    	uid_t st_uid;  // 用户ID
    	gid_t st_gid;  // 组ID
    	dev_t st_rdev;  // 设备文件的设备编号
    	off_t st_size;  // 文件字节数(文件大小)
    	blksize_t st_blksize;  // 块大小
    	blkcnt_t st_blocks;  // 块数
    	time_t st_atime;  // 最后一次访问时间
    	time_t st_mtime;  // 最后一次修改时间
    	time_t st_ctime;  // 最后一次改变时间(指属性)
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //a.txt
    hello,world
        
    //stat.c
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdio.h>
    
    int main()
    {
        struct stat statbuf;
        int ret = stat("a.txt", &statbuf);
        
        if (ret == -1)
        {
            perror("stat");
            return -1;
        }
        printf("size: %ld\n", statbuf.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

    stat函数与命令

    • 查看文件相关信息由两种方法
        1. 终端命令查看 如:stat 文件路径
        2. stat() 函数
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    int lstat(const char *pathname, struct stat *statbuf);
        作用:获取一个文件相关的信息(获取软连接文件的信息)
        参数:
            - pathname: 操作的文件的路径
            - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
        返回值:
            成功:返回0
            失败:返回-1 设置errno
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    软链接

    • ln -s a.txt b.txt 创建软链接文件 b.txt 指向 a.txt
    • 如果我们用stat()函数查看,那我们查看的就是 a.txt 文件,所以要想查看软链接文件,必须使用 lstat()函数
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdio.h>
    
    int main()
    {
    
        struct stat statbuf;
    	//这里查看的是b.txt(软链接),所以必须使用 lstat()函数
        int ret = lstat("b.txt", &statbuf);
    
        if (ret == -1)
        {
            perror("stat");
            return -1;
        }
    
        printf("size: %ld\n", statbuf.st_size);
        printf("ino: %ld\n", statbuf.st_ino);
        printf("dev: %ld\n", statbuf.st_dev);
    
        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

    # 6、stat结构体

    stat结构体

    # 7、st_mode变量

    st_mode-变量

    # 8、模拟实现 ls -l 命令

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <pwd.h>
    #include <grp.h>
    #include <time.h>
    #include <string.h>
    
    // 模拟实现 ls -l 指令
    // -rw-rw-r-- 1 nowcoder nowcoder 12 4月  12 14:43 a.txt
    
    int main(int argc, char *argv[])
    {
    
        // 因为我们的参数数组 argv 默认会有一个数据为我们可执行文件的文件名
        // 所以 argc (参数数量)<2 ,说明没给参数,所以我们要判断输入的参数是否正确
        if (argc < 2)
        {
            // 不正确,给出提示
            printf("%s filename\n", argv[0]);
            return -1;
        }
    
        // 通过stat函数获取用户传入的文件的信息
        struct stat st;
        int ret = stat(argv[1], &st);
        if (ret == -1)
        {
            perror("stat");
            return -1;
        }
    
        // 获取文件类型和文件权限
        char perms[11] = {0}; // 用于保存文件类型和文件权限的字符串
    
        // 获得的文件权限和权限掩码进行与运算,判断是什么类型的文件
        //注意在我自己的vscode里写下面代码时, 像S_IFMT这些会显示说是未定义标识符,要写__S_IFMT才正常,这应该是版本问题,我们直接		S_IFMT这样写,在gcc里是可以正常编译运行的
        switch (st.st_mode & S_IFMT)
        {
        case S_IFLNK:
            perms[0] = 'l';
            break;
        case S_IFDIR:
            perms[0] = 'd';
            break;
        case S_IFREG:
            perms[0] = '-';
            break;
        case S_IFBLK:
            perms[0] = 'b';
            break;
        case S_IFCHR:
            perms[0] = 'c';
            break;
        case S_IFSOCK:
            perms[0] = 's';
            break;
        case S_IFIFO:
            perms[0] = 'p';
            break;
        default:
            perms[0] = '?';
            break;
        }
    
        // 判断文件的访问权限
    
        // 文件所有者
        // 按位与,遇0为0.同1为1
        perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
        perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
        perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';
    
        // 文件所在组
        perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
        perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
        perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
    
        // 其他人
        perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
        perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
        perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';
    
        // 硬连接数
        int linkNum = st.st_nlink;
    
        // 文件所有者
        // 因为 st.st_uid 获得的是一个id号,但我们模拟 ls -l 指令获得的是用户名
        // 所以我们需要 pwd 库里的 getpwuid() 函数
        char *fileUser = getpwuid(st.st_uid)->pw_name;
    
        // 文件所在组
        // 同上,我们需要 grp 库里的 getgrgid() 函数
        char *fileGrp = getgrgid(st.st_gid)->gr_name;
    
        // 文件大小
        long int fileSize = st.st_size;
    
        // 获取修改的时间
        // st.st_mtime  获取到的是我们long int 型的数据,是从1970年到最后一次修改的秒数
        // 所以我们要用 time 头文件里的 ctime函数来将时间转换成本地的时间
        char *time = ctime(&st.st_mtime);
    
        // 因为转换后,会在末尾加上一个 \n(换行符),所以我们创建一个数组
        // 使用strncpy函数将 time数组的数据copy 到mtime数组里,复制的数据大小为 time数组大小减1,以此来去除末尾的换行符
        char mtime[512] = {0};
        strncpy(mtime, time, strlen(time) - 1);
    
        // 也可以直接  time[strlen(time)-1]='\0';去到换行符
    
        char buf[1024];
        // sprintf 把格式化的数据写入某个字符串中,这里就是把格式化的数据写进 buf 里
        // 注意这里的数据类型
        sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);
        // 输出字符串 buf
        printf("%s\n", buf);
    
        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
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120

    模拟实现-ls--l

    # 9、文件属性操作函数

    # 9.1 access 函数

    • int access(const char * pathname , int mode);
    #include <unistd.h>
    int access(const char *pathname, int mode);
        作用:判断某个文件是否有某个权限,或者判断文件是否存在
        参数:
            - pathname:判断的文件路径
            - mode:
                R_OK:判断是否有读权限
                W_OK: 判断是否有写权限
                X_OK:判断是否有执行权限
                F_OK: 判断文件是否存在
        返回值:成功返回0,失败返回 -1
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //access.c
    #include <unistd.h>
    #include <stdio.h>
    
    int main()
    {
        int ret = access("a.txt", F_OK);
        if (ret == -1)
        {
            perror("access");
            return -1;
        }
        printf("文件存在!!!\n");
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    access判断文件是否存在

    # 9.2 chmod 函数

    • int chmod(const char *pathname, mode_t mode);
    #include <sys/stat.h>
    int chmod(const char *pathname, mode_t mode);
        修改文件的权限
        参数:
            - pathname: 需要修改的文件的路径
            - mode:需要修改的权限值,八进制的数
        返回值:成功返回0,失败返回 -1
    
    1
    2
    3
    4
    5
    6
    7
    #include <sys/stat.h>
    #include <stdio.h>
    int main()
    {
        int ret = chmod("a.txt", 0775);
        if (ret == -1)
        {
            perror("chmod");
            return -1;
        }
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    chmod-修改文件权限

    # 9.3 chown 函数

    • *int chown(const char path,uid_t owner , gid_t group);

    # 9.4 truncate 函数

    • int truncate(const char *path, off_t length)
    #include <unistd.h>
    #include <sys/types.h>
    int truncate(const char *path, off_t length);
        作用:缩减或者扩展文件的尺寸至指定的大小
        参数:
            - path:需要修改的文件的路劲
            - length:需要最终文件变成的大小
        返回值:
            成功返回 0,失败返回 -1
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //truncate.c
    #include <unistd.h>
    #include <sys/types.h>
    #include <stdio.h>
    
    int main()
    {
        int ret = truncate("b.txt", 20);
    
        if (ret == -1)
        {
            perror("truncate");
            return -1;
        }
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    缩减文件

    • 💛:一开始文件大小是12,因为有一个字符串结束符,所以是12个;
    • ❤:把文件尺寸扩展为 20,扩充的部分(空洞)在被读取的时候是字节0;
    • 🧡:把文件尺寸缩减到 5;
    • 上图最后如果再执行一次 ll b.txt 会发现大小又变成了6,(这应该是我们使用 vim 打开并关闭文件后,自动给添加了一个结束符 (不确定是不是)),这里发现,扩展为20,在用vim 打开文件再关闭也会有相同的情况发生,即大小变为21

    # 10、目录操作函数

    # 10.1 mkdir 函数

    • int mkdir(const char *pathname, mode_t mode);
    • 创建目录
    #include <sys/stat.h>
    #include <sys/types.h>
    int mkdir(const char *pathname, mode_t mode);
        作用:创建一个目录
        参数:
            pathname:创建的目录的路劲
            mode:权限,八进制的数
        返回值:
            成功返回 0 ,失败返回 -1;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <stdio.h>
    
    int main()
    {
        // 注意:这里不要写成 777 ,不然是十进制数, 要写 0777才对
        int ret = mkdir("aaa", 0777);
        if (ret == -1)
        {
            perror("mkdir");
            return -1;
        }
    
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    mkdir函数

    • 🟨:aaa 权限错误是因为我们把代码写成了 mkdir("aaa" , 777);
    • 🟥:删除目录要加 -rf , rm -rf aaa ;
    • 💦:修改了权限,把777 改成了 0777,再经过与权限掩码取反后再 &(与) 运算,就得出图上的权限

    # 10.2 rmdir 函数

    • int rmdir( const char * pathname);
    • 删除目录,只能删除空目录,如果目录里有东西,就删不了

    # 10.3 rename 函数

    • int rename(const char *oldpath, const char *newpath);
    • 修改文件名
    #include <stdio.h>
    int rename(const char *oldpath, const char *newpath);
        参数:
            - 旧的名称 ,新的名称
        返回值 :
            成功返回 0 ,失败返回 -1
    
    1
    2
    3
    4
    5
    6
    #include <stdio.h>
    
    int main()
    {
        int ret = rename("aaa", "bbb");
        if (ret == -1)
        {
            perror("rename");
            return -1;
        }
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # 10.4 chdir 和 getcwd 函数

    • int chdir(const char *path);
      • 修改工作目录
    • char *getcwd(char *buf, size_t size);
      • 获取当前工作目录
    #include <unistd.h>
    int chdir(const char *path);
        作用:修改进程的工作目录
            比如在 /home/nowcoder 启动了一个可执行程序a.out,进程的工作目录 /home/nowcoder
        参数:
            - path:需要修改的工作目录
    #include <unistd.h>
    char *getcwd(char *buf, size_t size);
        作用: 获取当前的工作目录
        参数:
            - buf:存储的路径,指向的是一个数值(传出参数)
            - size:数值的大小
        返回值:
            返回的指向的一块内存,这个数据就是第一个参数
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <fcntl.h>
    
    int main()
    {
    
        // 获取当前的工作目录
        char buf[128];
        getcwd(buf, sizeof(buf));
        printf("当前的工作目录是:%s\n", buf);
    
        // 修改工作目录
        int ret = chdir("/home/nowcoder/Linux/lesson13");
    
        if (ret == -1)
        {
            perror("chdir");
            return -1;
        }
    
        // 创建一个新的文件
        int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664);
        if (fd == -1)
        {
            perror("open");
            return -1;
        }
    
        //关闭文件
        close(fd);
    
        // 获取当前的工作目录
        char buf1[128];
        getcwd(buf1, sizeof(buf1));
        printf("当前的工作目录是:%s\n", buf1);
    
        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

    获取与修改工作目录

    # 11、目录遍历函数

    • DIR *opendir(const char *name);

      • //打开一个目录
            #include <sys/types.h>
            #include <dirent.h>
            DIR *opendir(const char *name);
                参数:
                    -name:需要打开的目录的名称
                返回值:
                    DIR *类型,理解为目录流
                    错误返回 NULL
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
    • struct dirent *readdir(DIR *dirp);

      • //读取目录中的数据
            #include <dirent.h>
            struct dirent *readdir(DIR *dirp);
                - 参数: dirp 是opendir返回的结果
                -返回值:
                    struct dirent //代表读取到的文件的信息
                    //读取到了末尾或者失败了,返回NULL
        
        1
        2
        3
        4
        5
        6
        7

    # 11.1 dirent 结构体和 d_type

    struct dirent
    {
    	// 此目录进入点的inode,节点  整数   表示文件在虚拟文件系统中的位置 
    	ino_t d_ino; 
    	// 目录文件开头至此目录进入点的位移, 偏移量 目录中的位置  
    	off_t d_off; 
    	// d_name 的长度, 不包含NULL字符,文件名实际的大小
    	unsigned short int d_reclen; 
    	// d_name 所指的文件类型
    	unsigned char d_type; 
    	// 文件名 ,用来存储文件名
    	char d_name[256];
    };
    
    d_type:
    	DT_BLK - 块设备
    	DT_CHR - 字符设备
    	DT_DIR - 目录
    	DT_LNK - 软连接
    	DT_FIFO - 管道
    	DT_REG - 普通文件
    	DT_SOCK - 套接字
    	DT_UNKNOWN - 未知
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    • int closedir(DIR *dirp);

      • //关闭目录
            #include <sys/types.h>
            #include <dirent.h>
            int closedir(DIR *dirp);
                 - 参数: dirp 是opendir返回的结果
        
        1
        2
        3
        4
        5

    # 11.2 获取指定目录下普通文件的数量

    //readFileNum.c
    #include <sys/types.h>
    #include <dirent.h>
    #include <stdio.h>
    #include <string.h> //strcmp(),sprintf()
    #include <stdlib.h> //exit()
    
    int getFileNum(const char *path);//声明函数
    
    // 读取某个目录下所有的普通文件的个数
    int main(int argc, char *argv[])
    {
    
        if (argc < 2)
        {
            printf("%s path\n", argv[0]);
            return -1;
        }
    
        int num = getFileNum(argv[1]);
        printf("普通文件的个数为:%d\n", num);
        return 0;
    }
    
    // 用于获取目录下所有普通文件的个数
    // 我们传入进去的这个参数不需要被修改,所以加个 const
    int getFileNum(const char *path)
    {
        // 1.打开目录
        DIR *dir = opendir(path);
    
        if (dir == NULL)
        {
            perror("opendir");
            exit(0);
        }
    
        struct dirent *ptr;
    
        // 记录普通文件的个数
        int total = 0;
    
        while ((ptr = readdir(dir)) != NULL)
        {
            // 获取名称
            char *dname = ptr->d_name;
    
            // 忽略掉 . 和 ..
            // strcmp() 比较字符串,如果相等返回0
            if (strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0)
            {
                continue;
            }
    
            // 判断是否是普通文件还是目录
            // 这里 DT_DIR 在vscode里写时会显示未定义,应该是版本问题,我们gcc编译是能正常的
            if (ptr->d_type == DT_DIR)
            {
                // 目录,需要继续读取读取整个目录
                char newpath[256];
                sprintf(newpath, "%s/%s", path, dname);
                total += getFileNum(newpath);
            }
    
            if (ptr->d_type == DT_REG)
            {
                // 普通文件
                total++;
            }
        }
        // 关闭目录
        closedir(dir);
        return total;
    }
    
    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
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74

    获取普通文件数量

    • 🟡:没有传入参数(目录路径)
    • 🔴:正常运行查看
    • 🟢:没有权限/拒绝访问
    • 🌬:我们用 ll命令会发现,一个目录里除了本来又的文件或子目录外还有 ./(当前目录) ,../(上一级目录),所以我们代码里,需要除开这两个目录,注意:我们判断时直接判断 . 和 .. 就行 ,而不是 ./ 和 ../

    # 12、dup、dup2 函数

    # 12.1 dup() 函数

    • int dup(int oldfd);
      • 复制文件描述符
    #include <unistd.h>
    int dup(int oldfd);
        作用:复制一个新的文件描述符
            fd=3,int fd1 = dup(fd),
            fd指向的是 a.txt ,fd1也是指向 a.txt
            从空闲的文件描述符表里找一个最小的,作为新的拷贝的文件描述符
        参数:
        返回值:
    int dup2(int oldfd, int newfd);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //dup.c
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <string.h>
    
    int main()
    {
        int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
        int fd1 = dup(fd);
    
        if (fd1 == -1)
        {
            perror("dup");
            return -1;
        }
        printf("fd : %d , fd1 : %d\n", fd, fd1);
        
    	//关闭 fd
        close(fd);
        char *str = "hello,world";
        int ret = write(fd1, str, strlen(str));
        if (ret == -1)
        {
            perror("write");
            return -1;
        }
    	//关闭 fd1
        close(fd1);
        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

    dup函数

    • a.txt 会被改变,相当于把 a.txt 的文件信息又复制了一份,从空闲的文件描述符表里找一个最小的,作为新的拷贝的文件描述符,存放到文件描述符表中,所以我们使用 fd1 修改文件,也会修改 a.txt文件

    # 12.2 dup2() 函数

    • int dup2(int oldfd, int newfd);
      • 重定向文件描述符
    #include <unistd.h>
    int dup2(int oldfd, int newfd);
        作用:重定向文件描述符
        //oldfd 指向 a.txt,newfd 指向 b.txt
        //调用函数成功后: newfd 和 b.txt 做 💛close💖,newfd 指向了 a.txt
        //(这里并不是把newfd的值改成 oldfd的值,而是说 newfd 指向的信息不再是 b.txt而是 a.txt)
        //oldfd 必须是一个有效的文件描述符
        //如果oldfd 和 newfd值相同,相当于什么都没做
    	返回值:newfd
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //dup2.c
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <string.h>
    
    int main()
    {
    
        int fd1 = open("1.txt", O_RDWR | O_CREAT, 0664);
        if (fd1 == -1)
        {
            perror("open");
            return -1;
        }
    
        int fd2 = open("2.txt", O_RDWR | O_CREAT, 0664);
        if (fd2 == -1)
        {
            perror("open");
            return -1;
        }
    
        printf("fd1 : %d, fd2 : %d\n", fd1, fd2);
    
        // 返回值是 newfd的值 ,所以 fd 和fd2 相等
        int fd = dup2(fd1, fd2);
        if (fd == -1)
        {
            perror("dup2");
            return -1;
        }
    
        // 通过fd2去写数据,实际操作的是 1.txt,而不是2.txt
        char *str = "hello, dup2";
        int len = write(fd2, str, strlen(str));
    
        if (len == -1)
        {
            perror("write");
            return -1;
        }
    
        printf("fd1 : %d, fd2 : %d,fd : %d\n", fd1, fd2, fd);
    
        // 因为fd 和 fd2是相同的,所以我们关闭一个就可以
        close(fd1); // 关闭了 fd1指向的a.txt文件
        close(fd2); // 同上
    
        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
    47
    48
    49
    50
    51
    52
    53

    dup2函数

    • 可以看出 dup2() 的返回值是 newfd
    • 这里 fd2对 2.txt的指向被关闭,然后被指向了 fd1指向的 1.txt,所以数据写进了 1.txt 里

    # 13、fcntl() 函数

    • int fcntl(int fd, int cmd, ... /* arg */ );
      • 复制文件描述符 设置/获取文件的状态标志
    #include <unistd.h>
    #include <fcntl.h>
    
    int fcntl(int fd, int cmd, ... arg );
        参数:
            fd : 表示需要操作的文件描述符
            cmd : 表示对文件描述符进行什么操作
                - F_DUPFD 复制文件描述符,复制的是第一个参数 fd,得到一个新的文件描述符(返回值)
                    int ret = fcntl(fd,F_DUPFD);
    
                - F_GETFL : 获取指定的文件描述符文件状态 flag
                    获取的flag和我们通过open函数传递的flag是一个东西。
    
                -F_SETFL : 设置文件描述符文件状态 flag
                    必选项:O_RDONLY,O_WRONLY,O_RDWR 不可以被修改
                    可选项:O_APPEND, O_NONBLOCK
                        O_APPEND 表示追加数据
                        O_NONBLOCK 设置成非阻塞
    
             //阻塞和非阻塞:描述的是函数调用的行为。
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
    
        // 1.复制文件描述符写法:
        // int fd = open("1.txt",O_RDONLY);
        // int ret = fcntl(fd,F_DUPFD);
    
        // 2.修改或者获取文件状态的flag
        // 这里我们不能用O_RDONLY,要用O_RDWR,因为我们下面设置O_APPEND 只是往后面里追加,而不是有写的权限
        int fd = open("1.txt", O_RDWR);
        if (fd == -1)
        {
            perror("open");
            return -1;
        }
    
        // 获取文件描述符状态 flag
        int flag = fcntl(fd, F_GETFL);
        if (flag == -1)
        {
            perror("fcntl");
            return -1;
        }
        //必须以这种方式去修改权限,而不能直接就设置O_APPEND,不然原先的权限就被覆盖了
        flag |= O_APPEND; // flag = flag | O_APPEND;
    
        // 修改文件描述符状态的flag,给flag加入 O_APPEND 这个标记
        int ret = fcntl(fd, F_SETFL, flag); // 如果 ret =-1,就说明调用 fcntl失败
        if (ret == -1)
        {
            perror("fcntl");
            return -1;
        }
    
        char *str = "nihao";
        write(fd, str, strlen(str));
        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

    fcntl函数

    • 给 1.txt文件末尾追加了数据,如果我们直接用 O_WRONLY 来给文件写入数据,那就是从文件开头写入,会把文件原先的内容覆盖,所以我们要在末尾写数据,要修改文件描述符,开启O_APPEND 的权限
    编辑 (opens new window)
    上次更新: 2023/09/13, 12:29:52
    Linux开发工具
    进程概述

    ← Linux开发工具 进程概述→

    最近更新
    01
    51单片机及补充知识
    09-13
    02
    独立按键
    09-13
    03
    LCD1602液晶显示器
    09-13
    更多文章>
    Theme by Vdoing | Copyright © 2019-2023 Evan Xu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式