Linux 文件 IO 基础操作

Linux文件IO 与 C标准库文件IO

image-20220510161324134

image-20220510161515157

虚拟地址空间

虚拟地址通过页表映射到屋里内存,页表由操作系统维护并被处理器引用。

分为用户段(0~3G)和内核段(3G ~ 4G), 如下图所示。

Ref

image-20220510161614225

文件描述符

def: Linux 系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符,文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被打开的文件,所有执行I/O操作的系统调用都会通过文件描述符。

  • 每个文件描述符会与一个打开的文件相对应
  • 不同的文件描述符也可能指向同一个文件
  • 相同的文件可以被不同的进程打开,也可以在同一个进程被多次打开

image-20220510162116554

open函数

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
/*
	int open(const char *pathname, int flags); 打开一个已经存在的文件
	- pathname: 文件路径
	- flags: 对文件的操作权限设置
		O_RDONLY、O_WRONLY,ORDWR 三个设置是互斥的
	errno:属于 Linux 系统函数库,库中定义全局变量,记录的是最近的错误号。
	perror(const char *s):打印 error 对应的错误描述

    int open(const char *pathname, int flags, mode_t mode); 创建一个新的文件
	-pathname: 要创建的文件路径
	-flags:文件的操作权限和其他设置, 同上, O_CREAT,文件不存在,创建新文件
	- mode: 八进制数 
	最终权限是 mode & ~umask, umask 0002, ~umask 0775
	0777 & 0775 = 0775
	umask作用:抹去某些权限, 其他组不可以进行写的操作

*/

int main(int argc, char *argv[]) {
	// int fd = open("a.txt", O_RDONLY);
	// if (fd == -1) {
	// 	perror("open failed");
	// }
	// close(fd);
	int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
	if (fd == -1) {
		perror("create failed");
	}
	close(fd);
	return 0;
}

read & write

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

/*
	ssize_t read(int fd, void *buf, size_t count);
	- fd: 文件描述符
	- buf:缓冲区,读取数据存放的地方,数组的地址
	- count:指定的数组的大小
	返回值:
		> 0: 返回实际读取的大小字节
		=0:文件已经读完了
		-1: 失败

	ssize_t write(int fd, const void *buf, size_t count);
	- fd: 文件描述符
	- buf:缓冲区,要往磁盘写入的数据
	- count:要写的数据的实际大小
	返回值:
		> 0: 实际写入的字节大小
		-1: 失败
*/
void errif(bool flag, const char *msg) {
	if (flag) {
		perror(msg);
		exit(EXIT_FAILURE);
	}
}

int main() {
	// open打开文件
	int src_fd = open("./create.txt", O_RDONLY);
	errif(src_fd == -1, "open failed");
	// 创建一个新的文件
	int dest_fd = open("copy.txt", O_WRONLY | O_CREAT, 0664);
	errif(dest_fd == -1, "open failed");
	// 频繁的读写操作
	char buf[1024];
	bzero(&buf, sizeof(buf));
	int len = 0;
	while ((len = read(src_fd, buf, sizeof(buf))) > 0) {
		write(dest_fd, buf, len);
	}
	// 关闭文件
	close(src_fd);
	close(dest_fd);
	return 0;
}

lseek函数

  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)
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.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
			设置偏移量:当前位置 + 第二个参数 offset
			The file offset is set to its current location plus offset bytes.

	SEEK_END
			文件大小 + 第二个参数 offset
			The file offset is set to the size of the file plus offset bytes.

	返回文件指针所在位置

	移动文件指针到文件头
	lseek(fd, 0, SEEK_SET);
	获取当前文件指针的位置
	lseek(fd, 0, SEEK_CUR);
	获取文件长度
	lseek(fd, 0, SEEK_END);
	拓展文件的长度,当前文件 10b,增加 100 字节 100 字节
*/
int main() {
	int fd = open("create.txt", O_RDWR);

	if (fd == -1) {
		perror("open failed");
		return -1;
	}
	int ret = lseek(fd, 100, SEEK_END);
	if (ret == -1) {
		perror("lseek failed");
		return -1;
	}

	write(fd, " ", 1);
	close(fd);
	return 0;
}

stat & lstat查看文件属性参数

**def:stat:是用来获取文件属性的参数的命令,lstat:用来获取软链接文件属性的参数的命令

/*
    #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
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; // 最后一次改变时间(指属性)
};


*/

#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 功能


#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 12月  3 15:48 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;
}

access & chmod & chown & truncate 文件属性操作函数

access:判断某个文件是否有某个权限,或者判断文件是否存在

chomod:修改文件的权限

chown:修改当前文件所属用户组

truncate:缩减或者扩展文件的尺寸至指定的大小

mkdir & rmdir & rename & chdir & getcwd目录操作函数

mkdir:创建一个目录

rmdir: 删除一个目录

rename:更改目录名称

chdir: 更换目录

getcwd:当前位置

chdir 修改工作目录

/*

    #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: 数组的大小
        返回值:
            返回的指向的一块内存,这个数据就是第一个参数

*/
#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;
}

目录遍历函数

opendir: 打开目录

struct dirent *readdir(DIR *dirp);读取目录

closedir:关闭目录

递归的遍历统计目录文件个数

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


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

    // 关闭目录
    #include <sys/types.h>
    #include <dirent.h>
    int closedir(DIR *dirp);

*/
#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");
        exit(0);
    }

    struct dirent *ptr;

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

    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复制、重定向文件描述符

dup:复制当前文件描述符

dup2:重定向文件描述符

/*
    #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值相同,相当于什么都没有做
*/
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {

    int fd = open("1.txt", O_RDWR | O_CREAT, 0664);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664);
    if(fd1 == -1) {
        perror("open");
        return -1;
    }

    printf("fd : %d, fd1 : %d\n", fd, fd1);

    int fd2 = dup2(fd, fd1);
    if(fd2 == -1) {
        perror("dup2");
        return -1;
    }

    // 通过fd1去写数据,实际操作的是1.txt,而不是2.txt
    char * str = "hello, dup2";
    int len = write(fd1, str, strlen(str));

    if(len == -1) {
        perror("write");
        return -1;
    }

    printf("fd : %d, fd1 : %d, fd2 : %d\n", fd, fd1, fd2);

    close(fd);
    close(fd1);

    return 0;
}

fcntl 复制文件描述符 & 设置文件描述状态

fcntl:操作文件描述符函数,主要介绍复制文件描述符操作以及设置文件描述符状态


posted @ 2022-07-22 21:55  Lilyan&Code  阅读(179)  评论(0编辑  收藏  举报