- 对进行磁盘读写时,标准C库IO函数得到的缓冲区可以提高效率,降低写磁盘的次数;
- 当使用网络通信时,直接调用Linux系统的IO函数,提高响应速度,及时性。
- 标准C库IO设立缓冲区,当缓冲区满或者人为刷新时再调用内核 IO;
- 内核IO直接对磁盘进行操作。
虚拟内存,与进程相关。
一个程序进行起来对应一个进程,就会有一个虚拟地址空间,大小取决与CPU位数。
- 解决内存加载时产生的一些问题
- 解释 栈、堆 等模型
- 被内存管理单元(MMU)映射到真实物理地址上(虚拟内存管理)
Linux中设备都被虚拟成文件进行管理,
文件描述符用于指向操作文件,多个文件描述符可以指向同一个文件。
5. Linux系统IO函数Linux中 inode 的理解
什么是软链接、硬链接
硬链接 - 百度百科
基于 C 语言 LINUX 的文件操作
◼ 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); ◼ int stat(const char *pathname, struct stat *statbuf); ◼ int lstat(const char *pathname, struct stat *statbuf);
open() 函数中的 flags 参数(文件打开模式)
| 打开模式 | 含义 |
|---|---|
| O_CREAT | 必要时创建文件 |
| O_TRUNC | 删除全部现有数据 |
| O_APPEND | 维持现有数据,保存到其后面 |
| O_RDONLY | 只读打开 |
| O_WRONLY | 只写打开 |
| O_RDWR | 读写打开 |
终端输入 man 2 open 查看 open() 函数手册;(2代表 Linux 系统函数API,3 代表标准C库函数API文档)
// 打开一个已经存在的文件 #include#include #include int open(const char *pathname, int flags); 参数: - pathname:要打开的文件路径 - flags:对文件的操作权限设置还有其他的设置 O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的 返回值:返回一个新的文件描述符,如果调用失败,返回-1 // 错误号 errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。 // 打印errno对应的错误描述 #include void perror(const char *s); s参数:用户描述,比如hello,最终输出的内容是 hello:xxx(实际的错误描述) // 关闭文件 #include int close(int fd);
#include#include #include #include #include int main() { // 打开一个文件 int fd = open("a.txt", O_RDONLY); // 打开失败,错误号赋值给errno,用perror()查看具体错误 if(fd == -1) { // 文件描述符 (fd, file descriptor) perror("open"); // 参数由用户进行描述 } // 读写操作... // 关闭 close(fd); return 0; }
不存在 a.txt 文件,打开失败,提示如下:
5.2 open()创建新文件open: No such file or directory
#include#include #include int open(const char *pathname, int flags, mode_t mode); 参数: - pathname:要创建的文件的路径 - flags:【程序对文件的操作权限】和其他的设置 - 必选项:O_RDONLY, O_WRONLY, O_RDWR 这三个之间是互斥的 - 可选项:O_CREAT 文件不存在,创建新文件 flags参数是一个int类型的数据,占4个字节,32位。 flags 32个位,每一位就是一个标志位,不同标记间用 或| 结合。 - mode:八进制的数,表示创建出的新的【文件本身的操作权限】,比如:0775(0开头表示8进制) 三个对象 当前用户 | 同组用户 | 其他组 | 权限 r w x | r w x | r w x | 读-写-执行 二进制 1 1 1 | 1 1 1 | 1 1 1 | 0 或 1 ----- | ----- | ----- | 八进制 7 | 7 | 7 | 最终的权限是:mode & ~umask(掩码值,不同用户umask不一样,也可以更改) 0777 -> 111111111 & 0775 -> 111111101 (~ 002) ---------------------------- 111111101 ( rwxrwxr-x 其他组用户没有写权限) 按位与:0和任何数都为0 umask的作用就是抹去某些权限。
#include#include #include #include #include int main() { // 创建一个新的文件 int fd = open("create.txt", O_RDWR | O_CREAT, 0777); if(fd == -1) { perror("open"); } // 关闭 close(fd); return 0; }
cyf@cyf-virtual-machine:~/Linux/test09$ umask 0002 cyf@cyf-virtual-machine:~/Linux/test09$ ll create.txt -rwxrwxr-x 1 cyf cyf 0 5月 27 12:11 create.txt*5.3 read() / write() 读写文件
#includessize_t read(int fd, void *buf, size_t count); 参数: - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件 - buf:需要读取数据存放的地方,数组的地址(传出参数) - count:指定的数组的大小 返回值: - 成功:(偏移量移动已读大小) >0: 返回实际的读取到的字节数 =0:文件已经读取完了 - 失败:-1 ,并且设置errno,使用 perror(" "); 查看错误原因 #include ssize_t write(int fd, const void *buf, size_t count); 参数: - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件 - buf:要往磁盘写入的数据,数据 - count:要写的数据的实际的大小 返回值: 成功:实际写入的字节数 失败:返回-1,并设置errno,使用 perror(" "); 查看错误原因
#include5.4 lseek() 偏移量(光标)操作#include #include #include #include int main() { // 1.通过open打开english.txt文件 int srcfd = open("english.txt", O_RDONLY); if(srcfd == -1) { perror("open"); return -1; } // 2.创建一个新的文件(拷贝文件) int newfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664); if(newfd == -1) { perror("open"); return -1; } // 3.频繁的读写操作 char buf[1024] = {0}; int len = 0; while((len = read(srcfd, buf, sizeof(buf))) > 0) { // write(newfd, buf, len); if(write(newfd, buf, len) == -1) { perror("write"); return -1; } } // 4.关闭文件 close(newfd); close(srcfd); return 0; }
标准C库的函数 #includeint fseek(FILE *stream, long offset, int whence); Linux系统函数 #include #include off_t lseek(int fd, off_t offset, int whence); // whence + offset 参数: - 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) 注意:需要写一次数据
#include5.5 stat() / lstat() 获取文件信息#include #include #include #include #include // malloc int main() { int fd = open("hello.txt", O_RDWR | O_CREAT | O_TRUNC, 0777); // 读写 | 必要时创建 | 清除数据 if(fd == -1) { perror("open"); return -1; } write(fd, "0123456789", 10); // 0123456789 // 设置光标位置 int pos = lseek(fd, 4, SEEK_SET); // 0123 printf("zeroPos: %dn", pos); // 4 // 获取光标位置 int currPos = lseek(fd, 0, SEEK_CUR); printf("currPos: %dn", currPos); // 读取文件数据(光标随之移动) char buff[4] = {0}; read(fd, buff, sizeof(buff)); // 01234567 printf("read: %sn", buff); // 4567 currPos = lseek(fd, 0, SEEK_CUR); // 4 + 4 printf("currPos: %dn", currPos); // 从当前光标出写数据,并覆盖原有数据 write(fd, "abcde", 5); // 01234567abcde currPos = lseek(fd, 0, SEEK_CUR); // 8 + 5 printf("currPos: %dn", currPos); // 扩展文件的长度,空字符填充 int ret = lseek(fd, 9, SEEK_END); // 01234567abcde--------- if(ret == -1) { perror("lseek"); return -1; } // 需要在扩展的长度中或者之后写数据才算扩展完成 lseek(fd, -9, SEEK_CUR); write(fd, "123456789", 9); // 01234567abcde123456789 // 写入一个数据 write(fd, "o", 1); // 01234567abcde123456789o // 移动到起始位置,读取整个文件 int len = lseek(fd, 0, SEEK_END); printf("len: %dn", len); lseek(fd, 0, SEEK_SET); char* buf = (char*)malloc(len); read(fd, buf, len); printf("read: %sn", buf); free(buf); // 释放内存 // 关闭文件 close(fd); return 0; }
返回文件的相关信息(stat 结构体)
#include#include #include // 获取具体文件的信息 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
- 软链接:
建立新文件,类同与windos的快捷方式,给文件创建一个快速的访问路径,它依赖于原文件,与普通文件没什么不同,iode都指向同一个文件在硬盘中的区块。当原文件出现问题后,该链接不可用。
- 硬链接:
硬链接一旦建立,源文件和链接文件的任意一方修改则会同步修改。本质上是同一个文件,不建立新的文件。
#include#include #include #include int main() { struct stat statbuf; struct stat lstatbuf; int ret = stat("b.txt", &statbuf); // 被指向文件 int lret = lstat("b.txt", &lstatbuf); // 软链接的文件本身 if(ret == -1) { perror("stat"); return -1; } printf("size: %ldn", statbuf.st_size); printf("lsize: %ldn", lstatbuf.st_size); return 0; }
利用 stat 模仿 ls -l file 功能size: 10
lsize: 5
#include5.6 文件属性操作函数#include #include #include #include // getpwuid() #include // getgrgid() #include // ctime() #include // strncpy(), strlen() // 模拟实现 ls -l 指令 // -rw-rw-r-- 1 nowcoder nowcoder 12 12月 3 15:48 a.txt int main(int argc, char * argv[]) { // 判断输入的参数是否正确 if(argc < 2) { // argc 形参个数 printf("%s filenamen", argv[0]); // argv[0]为当前程序名称 return -1; } // 通过stat函数获取用户传入的文件的信息 struct stat st; int ret = stat(argv[1], &st); // argv[1] 为真正传递的参数 if(ret == -1) { perror("stat"); return -1; } char perms[11] = {0}; // -rw-rw-r-- 10个字符 + 1个空字符 表字符串, 用于保存文件类型和文件权限的字符 // 获取文件类型 st_mode 二进制前四位 switch(st.st_mode & S_IFMT) { // st.st_mode & 1111 000 000 000 000 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; } // 判断文件的访问权限 st_mode 二进制后九位 // 文件所有者 perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-'; // 按位与,若某一位为1 则为非0 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; // ID -> 结构体 -> 用户名 // 文件所在组 char * fileGrp = getgrgid(st.st_gid)->gr_name; // ID -> 结构体 -> 组名 // 文件大小 long int fileSize = st.st_size; // 获取修改的时间 char * time = ctime(&st.st_mtime); // st_mtime 为1970.1.1 0时到目前的秒数 // 以上ctime转换后 time 自带 回车换行,此处减去末尾 回车换行符 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("%sn", buf); return 0; }
◼ int access(const char *pathname, int mode); // 判断文件权限 或 是否存在 ◼ int chmod(const char *filename, int mode); // 修改文件权限 change mode ◼ int chown(const char *path, uid_t owner, gid_t group); // 修改文件所有者和所在组 change owner ◼ int truncate(const char *path, off_t length); // 缩减 或者 扩展文件大小
- access.c
#include#include int main() { int ret = access("a.txt", F_OK); if(ret == -1) { perror("access"); }else{ printf("文件存在!!!n"); } ret = access("a.txt", W_OK); if(ret == -1) { perror("access"); }else{ printf("文件可写!!!n"); } ret = access("a.txt", X_OK); if(ret == -1) { perror("access"); }else{ printf("可执行!!!n"); } return 0; }
- chmod.c
#include#include int main() { int ret = chmod("a.txt", 0775); if(ret == -1) { perror("chmod"); return -1; } return 0; }
- truncate.c
#include5.7 目录操作函数#include #include int main() { int ret = truncate("b.txt", 5); if(ret == -1) { perror("truncate"); return -1; } return 0; }
◼ 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); // 获取当前工作目录
- mkdir.c
#include#include #include int main() { int ret = mkdir("aaa", 0777); if(ret == -1) { perror("mkdir"); return -1; } return 0; }
- rename.c
#includeint main() { int ret = rename("aaa", "bbb"); if(ret == -1) { perror("rename"); return -1; } return 0; }
- chdir(), getcwd()
#include5.8 目录遍历函数#include #include #include #include int main() { // 获取当前的工作目录 char buf[128]; getcwd(buf, sizeof(buf)); printf("当前的工作目录是:%sn", buf); // /home/cyf/Linux/test14 // 修改工作目录 int ret = chdir("/home/cyf/Linux/test14/bbb"); 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("当前的工作目录是:%sn", buf1); // /home/cyf/Linux/test14/bbb return 0; }
◼ DIR *opendir(const char *name); // 打开一个目录,返回目录流 ◼ struct dirent *readdir(DIR *dirp); // 读取目录数据,目录流往后移 ◼ int closedir(DIR *dirp); // 关闭目录
- 获取某个文件夹下所有普通文件的个数(递归子文件夹)
#include5.9 复制、重定向文件描述符#include #include #include #include int getFileNum(const char * path); // 读取某个目录下所有的普通文件的个数 int main(int argc, char * argv[]) { if(argc < 2) { printf("%s pathn", argv[0]); return -1; } int num = getFileNum(argv[1]); printf("普通文件的个数为:%dn", 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; }
◼ int dup(int oldfd); 复制文件描述符 ◼ int dup2(int oldfd, int newfd); 重定向文件描述符
#include#include #include #include #include #include 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 : %dn", fd, fd1); close(fd); // 关闭原描述符 char * str = "hello,world"; int ret = write(fd1, str, strlen(str)); // 在复制的描述符上操作文件 if(ret == -1) { perror("write"); return -1; } close(fd1); return 0; }
#include5.10 fcntl()函数#include #include #include #include #include int main() { int fd = open("1.txt", O_RDWR | O_CREAT, 0664); // oldfd if(fd == -1) { perror("open"); return -1; } int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664); // newfd if(fd1 == -1) { perror("open"); return -1; } printf("fd : %d, fd1 : %dn", fd, fd1); int fd2 = dup2(fd, fd1); // fd1, fd2 都指向fd指向的同一个文件1.txt 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 : %dn", fd, fd1, fd2); close(fd); close(fd1); return 0; }
◼ int fcntl(int fd, int cmd, ... );
复制文件描述符
设置/获取文件的状态标志
#include
#include
int fcntl(int fd, int cmd, ...);
参数:
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 表示追加数据
NONBLOK 设置成非阻塞(阻塞和非阻塞:描述的是函数调用的行为。)
...
#include#include #include #include int main() { // 1.复制文件描述符 // int fd = open("1.txt", O_RDONLY); // int ret = fcntl(fd, F_DUPFD); // 2.修改或者获取文件状态flag int fd = open("1.txt", O_WRONLY); if(fd == -1) { perror("open"); return -1; } // 获取文件描述符状态flag int flag = fcntl(fd, F_GETFL); if(flag == -1) { perror("fcntl"); return -1; } flag |= O_APPEND; // flag = flag | O_APPEND // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记 int ret = fcntl(fd, F_SETFL, flag); if(ret == -1) { perror("fcntl"); return -1; } char * str = "nnihao"; write(fd, str, strlen(str)); close(fd); return 0; }



