【Linux系统编程】Linux系统下文件IO函数

文件IO

标准C库IO函数
  • 标准C库IO函数是可跨平台的,效率会更高。
  • Linux系统的IO API 相比 标准C库IO函数 更低层,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V7KvUnCx-1658458538110)(C:\Users\85420\AppData\Roaming\Typora\typora-user-images\image-20220720153923250.png)]

Linux:文件描述符(Windows:文件句柄)

I/O缓冲区:当向文件中写入数据时,会先将数据放入I/O缓冲区,等待缓冲区满的时候,再调用Linux的IO API将缓冲区的数据写入文件中,从而提高效率(因为写入文件 往硬盘中写入数据本身效率很低,所以尽量少地调用,降低写磁盘的次数)。

Linux的IO API没有I/O缓冲区。

标准C库IO vs. Linux系统IO

虚拟地址空间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jT5AyVDW-1658458538111)(C:\Users\85420\AppData\Roaming\Typora\typora-user-images\image-20220720162836510.png)]

MMU(逻辑管理单元,位于CPU):将虚拟地址空间映射到真实的物理内存上

栈空间:从高地址往低地址存储数据

堆空间:从低地址往高地址存储数据

环境变量:env

内核区:普通用户没有读写权限,可以通过Linux系统调用(系统API函数)对数据进行操作。

文件描述符

  • PCB:struct

  • 文件描述符表:array 默认大小1024

    • 前三个位置默认被占用:标准输入、标准输出、标准错误 (默认打开状态)

      指向的文件都是当前终端(Linux:一切皆文件)

  • 一个文件可以被打开fopen多次,但是对应的文件描述符是不同的。

  • 每次fopen时,都会自动分配文件描述符表中没有被占用的最小文件描述符。

Linux系统IO函数

标准C库的IO函数和Linux系统IO函数是对应的。

int open (const char *pathname,int flags );
int open (const char *pathname,int flags, mode_t mode);
int close (int fd);
ssize_t read(int fd,void *buf, size_t count);
ssize_t write(int fd,const void *buf, size_t count);
off_t lseek (int fd, off_t offset, int whence);
# Linux系统的IO函数 open函数
man 2 open
# 标准C库的IO函数 (位于手册第三版)
man 3

注意:大部分Linux函数如果调用失败会返回 -1.

open函数
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>  //函数的声明
	#include <unistd.h>
	#include <stdio.h>

    // 1. 打开一个已经存在的文件
    int open(const char *pathname, int flags);  // flags 定义成了宏 放在前两个头文件中
        参数:
            - pathname: 要打开的文件路径
            - flags: 对文件的操作权限设置还有其他的设置  
                - 必选项 O_RDONLY, O_WRONLY, or O_RDWR 这三个设置是互斥的
        返回值:返回一个新的文件描述符;如果调用失败,会返回-1.
        return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).
    
    errno: 属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。
    
    #include <stdio.h>
    void perror(const char *s);
        - s: 用户描述,比如hello,最终的输出是 hello:xxx(实际的错误描述)
    作用:打印errno对应的错误描述
    
    int main(){
    int fd = open("a.txt", O_RDONLY);
    if(fd == -1) perror("open");
    // 读写操作
    // ...
    // 关闭文件(文件描述符,使得它不再指向任何一个文件,并且可重用)
    close(fd);

    return 0;
	}


    // 2. 新建并打开一个文件
    int open(const char *pathname, int flags, mode_t mode);
        参数:
            - pathname: 要创建的文件路径
            - flags: 对文件的操作权限和其他的设置
                - 必选项 O_RDONLY, O_WRONLY, or O_RDWR 这三个设置是互斥的
                - 可选项 O_CREAT 文件不存在,创建新文件
            - mode: 八进制的数,表示用户对创建出的新的文件操作权限。如:0775(0:代表八进制数)
                rwx: 111(2) -> 7(8) -wx: 3(8) r--: 4(8)
                so -rwxrwxrwx -> 0777(8)
            最终的新建文件权限权限:the mode of the created file is (mode & ~umask)
            不同用户 umask的值 不同
            eg.     0777 (mode)    -> 111111111
                &   0775 (~umask)  -> 111111101
                    ---------------------------
                    0775              111111101 (按位与:0和任何数都为0)
            umask作用:抹去某些权限。
            也可以通过 umask 022 人为修改 umask (不过只在当前终端有效)

            flags参数是一个int类型的数据,占4个字节,32位。
            flags 32个位,每一位都是一个标志位,代表一种情况。
                    
	// example
    int main(){
    // 创建一个新的文件
    umask(003);
    int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
    if(fd == -1) perror("open");
    // 关闭文件
    close(fd);
    return 0;
    }

注意:

# 文件权限
boyangcao@MyLinux:~/Linux/Lesson09$ ll
总用量 24
drwxrwxr-x  2 boyangcao boyangcao 4096 721 11:18 ./
drwxrwxr-x 11 boyangcao boyangcao 4096 721 10:49 ../
-rw-rw-r--  1 boyangcao boyangcao    0 721 11:18 create.c
# 第一个字母代表当前文件的类型
# 后9个字母代表权限 每三个一组
# 第一组:当前用户对文件的权限
# 第二组:当前用户所在的组对文件的权限
# 第三组:其他组对文件的权限
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。

实际应用:

#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. 频繁的读写操作
    char buf[1024] = {0};
    int len = 0;
    while((len = read(srcfd, buf, sizeof(buf))) > 0) write(destfd, buf, len);
    
    // 4. 关闭文件
    close(destfd);
    close(srcfd);
    return 0;
}
lseek函数
    标准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()得到的,通过这个文件描述符操作该文件。
            - offset: 偏移量
            - whence: 
                SEEK_SET: The file offset is set to offset bytes. 
                    设置文件指针的偏移量:第二个参数
                SEEK_CUR: The file offset is set to its current location plus offset bytes.
                    设置偏移量:当前位置 + 第二个参数 offset 的值
                SEEK_END: The file offset is set to the size of the file plus offset bytes.
                    设置偏移量:文件大小 + 第二个参数 offset 的值
        返回值:返回文件指针最终所在的位置(offset)。
        作用:
            1. 移动文件指针到头文件
                lseek(fd, 0, SEEK_SET);
            2. 获取当前文件指针的位置
                lseek(fd, 0, SEEK_CUR);
            3. 获取文件长度
                lseek(fd, 0, SEEK_END);
            4. 拓展文件的长度: 当前文件10b -> 100b, 增加了100个字节。
                lseek(fd, 100, SEEK_END);
                注意:在拓展文件长度的时候,在后面需要写入一次数据。
                作用:在下载的时候 先提前占据目标大小的空间,再慢慢进行下载替换空数据。  

举例:

#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");
        return -1;
    }
    // 拓展文件的长度
    int ret = lseek(fd, 100, SEEK_END);
    if(ret == -1)
    {
        perror("lseek");
        return -1;
    }
    // 写入一个空数据
    write(fd, " ", 1);
    // 关闭文件
    close(fd);
    return 0;
}
stat/lstat函数

stat 结构体:

st_mode: 16位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HvQq1c2f-1658458538112)(C:\Users\85420\AppData\Roaming\Typora\typora-user-images\image-20220721151228017.png)]

文件类型在Linux系统中一共有7种。

获取该文件是否有相应权限: 将st_mode 与目标权限做 按位与 操作。

获取该文件类型:st_mode & S_IFMT

(S_IFMT == (2) 1111 0000 0000 0000)

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>

    int stat(const char *pathname, struct stat *statbuf);
        作用:获取一个文件相关的一些信息
        参数:  
            - pathname: 操作的文件路径
            - statbuf: 结构体变量,传出参数,用于保存获取到的文件信息。
        返回值:
            成功:返回0
            失败:返回-1, errno将被设置。

    int lstat(const char *pathname, struct stat *statbuf);
        作用:获取一个文件相关的一些信息 
        参数:  
            - pathname: 操作的文件路径
            - statbuf: 结构体变量,传出参数,用于保存获取到的文件信息。
        返回值:
            成功:返回0
            失败:返回-1, errno将被设置。
    
    ps. 当有一个软链接
    ln -s a.txt b.txt;
    stat("b.txt", &statbuf); 获取的是软链接指向的文件的信息
    lstat("b.txt", &statbuf); 获取的是软链接本身的信息

举例:

#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;
}
模拟实现 ls -l a.txt 指令
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <time.h>
#include <grp.h>
#include <string.h>

// 模拟实现 ls -l 指令
// -rw-rw-r-- 1 boyangcao boyangcao 15 7月  21 15:27 a.txt
int main(int argc, char* argv[])
{
    // 判断输入的参数是否正确
    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}; // 用于保存文件类型和文件权限的字符串
    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;        
    }

    // 判断文件的访问权限

    // 文件所有者
    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;

    // 文件所有者
    char * fileUser = getpwuid(st.st_uid)->pw_name;

    // 文件所在组
    char * fileGrp = getgrgid(st.st_gid)->gr_name;

    // 文件大小
    long int fileSize = st.st_size;

    // 获取修改时间
    char* time = ctime(&st.st_mtime);
    char mtime[512] = {0};
    strncpy(mtime, time, strlen(time) - 1);

    char buf[1024];
    sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);

    printf("%s\n", buf);

    return 0;
}
文件属性操作函数
// 判断文件权限/是否存在
int access ( const char *pathname,int mode);
// 修改文件权限
int chmod ( const char *filename,int mode);
// 修改文件所有者/所在组
int chown (const char *path,uid_t owner, gid_t group);
// 缩减、扩展文件大小
int truncate (const char *path, off_t length);

#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
        作用:修改文件的权限
        参数:
            - pathname: 需要修改的文件路径
            - mode: 需要修改的权限值,八进制的数
        返回值:
            成功:返回0
            失败:返回-1
int ret = chmod("a.txt", 0775);


#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
        作用:缩减或扩展文件的尺寸至指定的大小
        参数:
            - path: 需要修改文件的路径
            - length: 需要最终文件变成的大小
        返回值:成功返回0 ,失败返回-1
int ret = truncate("b.txt", 5);
目录操作函数
// 建立目录
int mkdir (const char *pathname,mode_t mode);
// 删除目录(只能删除空目录)
int rmdir (const char *pathname);
// 对目录重命名
int rename (const char *oldpath,const char *newpath);
// 更改当前工作目录
int chdir (const char *path);
// 获取当前工作路径
char *getcwd (char *buf,size_t size);

#include <sys/stat.h>
#include <sys/types.h>

int mkdir(const char *pathname, mode_t mode);
        作用:创建一个目录
        参数:
            - pathname: 创建目录的路径
            - mode: 权限,八进制的数
        返回值: 成功返回0,失败返回-1
int ret = mkdir("aaa", 0777);

#include <unistd.h>
int chdir(const char *path);
        作用:修改进程的工作目录
            比如在/home/nowcoder 启动了一个可执行程序,进程的工作目录 /home/nowcoder
        参数:
            path: 需要修改的工作目录
int ret = chdir("/home/boyangcao/Linux/Lesson13");
                
#include <unistd.h>
char *getcwd(char *buf, size_t size);
        作用:获取当前工作目录
        参数:
            - buf: 存储的路径,指向一个数组(传出参数)- size: 指定的数组大小。
        返回值:
            返回的是指向的一块内存,这个数据就是第一个参数。
getcwd(buf, sizeof(buf));
目录遍历函数
// 打开一个目录
DIR *opendir ( const char *name);
// 读取一个目录
struct dirent *readdir (DIR *dirp);
// 关闭一个目录
int closedir (DIR *dirp);

#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
        作用:打开一个目录
        参数:
            -name: 需要打开的目录的名称
        返回值:
            DIR * 类型: 理解为目录流信息
            错误返回NULL
DIR * dir = opendir(path);

#include <dirent.h>
struct dirent *readdir(DIR *dirp);
        作用:读取目录中的数据
        参数:
            - dirp: 通过opendir返回的结果
        返回值:
            struct dirent, 代表读取到的信息
            读取到了末尾或者失败了,返回NULL
struct dirent *ptr = readdir(dir);
                
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
        作用:关闭目录
        参数:
            - dirp: 通过opendir返回的结果
closedir(dir);

dirent结构体和d_type:

注意:

DT_REG不是ISO C11扩展的一部分。设置-std=c11仅严格启用C11标准中定义的功能。

可以使用功能宏启用其他扩展。正如readdir手册提到的,您需要_DEFAULT_SOURCE宏来启用文件类型常量。

在包含dirent.h之前,可以在源代码中执行此操作

#define _DEFAULT_SOURCE
#include <dirent.h>

或通过命令行作为编译器选项

cc -std=c11 -D_DEFAULT_SOURCE -Wall -Werror -pedantic

示例:读取目录下所有普通文件的个数:

#define _DEFAULT_SOURCE

#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

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;
}

// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path)
{
    //1. 打开目录
    DIR * dir = opendir(path);
    if(dir == NULL)
    {
        perror("opendir");
        return 0;
    }

    // 记录普通文件的个数
    int total = 0;

    struct dirent *ptr;

    while((ptr = readdir(dir)) != NULL)
    {
        // 获取名称
        char* dname = ptr->d_name;

        // 忽略掉. 和 ..
        if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) continue;
        // 判断是否是普通文件还是目录
        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;
}
dup/dup2函数
int dup (int oldfd); //复制文件描述符
int dup2 (int oldfd, int newfd) ; //重定向文件描述符

#include <unistd.h>

int dup(int oldfd);
        作用:复制一个新的文件描述符,并和oldfd指向同一个文件
        fd = 3; fd1 = dup(fd)
        fd指向的是1.txt,则fd1也是指向1.txt
        从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符。
int fd1 = dup(fd);

#include <unistd.h>
int dup2(int oldfd, int newfd);
        作用:重定向文件描述符。
        假如oldfd 指向a.txt, newfd 指向b.txt
        调用函数成功后:newfd 和 b.txt 做close,newfd 指向了 a.txt
        oldfd 必须是一个有效的文件描述符
        oldfd 和 newfd 值相同,相当于什么也没有做。
        返回值:On success, these system calls return the new file descriptor.  
               On error, -1 is returned, and errno is set appropriately.
int fd2 = dup2(fd, fd1);
fcntl函数
// 1. 复制文件描述符
// 2. 设置/获取文件的状态标志
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 设置成非阻塞
            阻塞和非阻塞:描述的是函数调用的行为

// 获取文件描述符状态的flag
int flag = fcntl(fd, F_GETFL);
flag |= O_APPEND;
// 修改文件描述符状态的flag,给flag加入O_APPEND标记
int ret = fcntl(fd, F_SETFL, flag);
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Beyon.sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值