一. Linux 常用文件 IO 接口
1.1. 文件描述符
1.1.1. 文件描述符的本质是一个数字, 这个数字本质上是进程表中文件描述符表的一个表项, 进程通过文件描述符作为 index 去索引查表得到文件表指针, 再间接访问得到这个文件对应的文件表.
1.1.2. 文件描述符这个数字是 open 系统调用内部由操作系统自动分配的, 操作系统分配这个 fd 时也不是随意分配, 也是遵照一定的规律的: fd 从 0 开始依次增加. fd 也是有最大限制的, 在 Linux 的早期版本中(0.11)fd 最大是 20, 所以当时一个进程最多允许打开 20 个文件. Linux 中文件描述符表是个数组(不是链表), 所以这个文件描述符表其实就是一个数组, fd 是 index, 文件表指针是 value. 当我们去 open 时, 内核会从文件描述符表中挑选一个最小的未被使用的数字给我们返回.
1.1.3. fd 中 0,1,2 已经默认被系统占用了, 因此用户进程得到的最小的 fd 就是 3 了. 这三个文件对应的 fd 就是 0,1,2. 这三个文件分别叫 stdin,stdout,stderr. 也就是标准输入, 标准输出, 标准错误.
1.2. open
1.2.1. 在 Linux 系统中要操作一个文件, 一般是先 open 打开一个文件, 得到一个文件描述符, 然后对文件进行读写操作(或其他操作), 最后 close 关闭文件即可
1.2.2. 文件平时是存在块设备中的文件系统中的, 我们把这种文件叫静态文件. 当我们去 open 打开一个文件时, Linux 内核做的操作包括: 内核在进程中建立了一个打开文件的数据结构, 记录下我们打开的这个文件; 内核在内存中申请一段内存, 并且将静态文件的内容从块设备中读取到内存中特定地址管理存放(叫动态文件).
1.2.3. 打开文件后, 以后对这个文件的读写操作, 都是针对内存中这一份动态文件的, 而并不是针对静态文件的. 当我们对动态文件进行读写后, 此时内存中的动态文件和块设备中的静态文件就不同步了, 当我们 close 关闭动态文件时, close 内部内核将内存中的动态文件的内容去更新 (同步) 块设备中的静态文件. 这样做主要由于: 块设备本身有读写限制(回忆 NnadFlash,SD 等块设备的读写特征), 本身对块设备进行操作非常不灵活. 而内存可以按字节为单位来操作, 而且可以随机操作(内存就叫 RAM,random), 很灵活. 所以内核设计文件操作时就这么设计了.
1.3. read
- ssize_t read(int fd, void *buf, size_t count);
- View Code
a. fd 表示要读取哪个文件, fd 一般由前面的 open 返回得到
b. buf 是应用程序自己提供的一段内存缓冲区, 用来存储读出的内容
c. count 是我们要读取的字节数
d. 返回值 ssize_t 类型是 Linux 内核用 typedef 重定义的一个类型(其实就是 int), 返回值表示成功读取的字节数.
1.4. write
- ssize_t write(int fd, const void *buf, size_t count);
- View Code
1.4.1. 写入用 write 系统调用, write 的原型和理解方法和 read 相似
1.5. lseek
- off_t lseek(int fd, off_t offset, int whence);
- View Code
1.5.1. 文件指针: 当我们要对一个文件进行读写时, 一定需要先打开这个文件, 所以我们读写的所有文件都是动态文件. 动态文件在内存中的形态就是文件流的形式.
1.5.2. 在动态文件中, 我们会通过文件指针来表征这个正在操作的位置. 所谓文件指针, 就是我们文件管理表这个结构体里面的一个指针. 所以文件指针其实是 vnode 中的一个元素. 这个指针表示当前我们正在操作文件流的哪个位置. 这个指针不能被直接访问, Linux 系统用 lseek 函数来访问这个文件指针.
1.5.3. 当我们打开一个空文件时, 默认情况下文件指针指向文件流的开始. 所以这时候去 write 时写入就是从文件开头开始的. write 和 read 函数本身自带移动文件指针的功能, 所以当我 write 了 n 个字节后, 文件指针会自动向后移动 n 位. 如果需要人为的随意更改文件指针, 那就只能通过 lseek 函数了
1.5.4. 用 lseek 计算文件长度(length = lseek(fd,0,SEEK_END))
1.6. close
- int close(int fd);
- View Code
1.6.1. 关闭打开的文件
PS: 实时查 man 手册
(1)当我们写应用程序时, 很多 API 原型都不可能记得, 所以要实时查询, 用 man 手册
(2)man 1 xx 查 Linux shell 命令, man 2 xxx 查 API, man 3 xxx 查库函数
二. open 函数的 flag 详解
2.1. 读写权限
a. O_RDONLY 就表示以只读方式打开,
b. O_WRONLY 表示以只写方式打开,
c. O_RDWR 表示以可读可写方式打开
2.2. 打开存在并有内容的文件时
2.3. 打开不存在的文件时
- 2.4. O_NONBLOCK
- 2.5. O_SYNC
三. 文件读写的一些细节
- 3.1. errno
- 3.2. perror
3.3. 文件 IO 效率和标准 IO
四. 退出进程方式
4.1. 在 main(main 函数由其父进程调用, 故返回后进程就 over)用 return, 一般原则是程序正常终止 return 0, 如果程序异常终止则 return -1.
4.2. 正式终止进程 (程序) 应该使用 exit 或者_exit 或者_Exit 之一.
五. 文件共享的实现方式
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <string.h>
- int main(int argc, char *argv[])
- {
- int fd1 = -1, fd2 = -1; // fd 就是 file descriptor, 文件描述符
- char buf[100] = {0};
- char writebuf[20] = "l love linux";
- int ret = -1;
- // 第一步: 打开文件
- fd1 = open("a.txt", O_RDWR);
- fd2 = open("a.txt", O_RDWR);
- //fd = open("a.txt", O_RDONLY);
- if ((-1 == fd1) || (fd2 == -1)) // 有时候也写成: (fd < 0)
- {
- //printf("\n");
- perror("文件打开错误");
- // return -1;
- _exit(-1);
- }
- else
- {
- printf("文件打开成功, fd1 = %d. fd2 = %d.\n", fd1, fd2);
- }
- #if 0
- // 第二步: 读写文件
- // 写文件
- ret = write(fd, writebuf, strlen(writebuf));
- if (ret < 0)
- {
- //printf("write 失败.\n");
- perror("write 失败");
- _exit(-1);
- }
- else
- {
- printf("write 成功, 写入了 %d 个字符 \ n", ret);
- }
- #endif
- #if 1
- while(1)
- {
- // 读文件
- memset(buf, 0, sizeof(buf));
- ret = read(fd1, buf, 2);
- if (ret < 0)
- {
- printf("read 失败 \ n");
- _exit(-1);
- }
- else
- {
- //printf("实际读取了 %d 字节.\n", ret);
- printf("fd1:[%s].\n", buf);
- }
- sleep(1);
- // 读文件
- memset(buf, 0, sizeof(buf));
- ret = read(fd2, buf, 2);
- if (ret < 0)
- {
- printf("read 失败 \ n");
- _exit(-1);
- }
- else
- {
- //printf("实际读取了 %d 字节.\n", ret);
- printf("fd2:[%s].\n", buf);
- }
- }
- #endif
- // 第三步: 关闭文件
- close(fd1);
- close(fd2);
- _exit(0);
- }
- View Code
来源: http://www.bubuko.com/infodetail-2975227.html