1、文件描述符
(1) 文件描述符的本质是一个数字,这个数字本质上是进程表中文件描述符表的一个表项,进程通过文件描述符作为 index 去索引查表得到文件表指针,再间接访问得到这个文件对应的文件表。
(2) 文件描述符这个数字是 open 系统调用内部由操作系统自动分配的,操作系统分配这个 fd 时也不是随意分配,也是遵照一定的规律的,我们现在就要研究这个规律。
(3) 操作系统规定,fd 从 0 开始依次增加。fd 也是有最大限制的,在 linux 的早期版本中(0.11)fd 最大是 20,所以当时一个进程最多允许打开 20 个文件。linux 中文件描述符表是个数组(不是链表),所以这个文件描述符表其实就是一个数组,fd 是 index,文件表指针是 value
(4) 当我们去 open 时,内核会从文件描述符表中挑选一个最小的未被使用的数字给我们返回。也就是说如果之前 fd 已经占满了 0-9,那么我们下次 open 得到的一定是 10.(但是如果上一个 fd 得到的是 9,下一个不一定是 10,这是因为可能前面更小的一个 fd 已经被 close 释放掉了)
(5)fd 中 0、1、2 已经默认被系统占用了,因此用户进程得到的最小的 fd 就是 3 了。
(6)linux 内核占用了 0、1、2 这三个 fd 是有用的,当我们运行一个程序得到一个进程时,内部就默认已经打开了 3 个文件,这三个文件对应的 fd 就是 0、1、2。这三个文件分别叫 stdin、stdout、stderr。也就是标准输入、标准输出、标准错误。
(7) 标准输入一般对应的是键盘(可以理解为:0 这个 fd 对应的是键盘的设备文件),标准输出一般是 LCD 显示器(可以理解为:1 对应 LCD 的设备文件)
(8)printf 函数其实就是默认输出到标准输出 stdout 上了。stdio 中还有一个函数叫 fpirntf,这个函数就可以指定输出到哪个文件描述符中。
2、常用 API
open、close、write、read、lseek
2.1OPEN
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode)
const char *pathname 为需要打开的文件、
flags 通过一些宏设置读写权限
O_RDONLY、 O_WRONLY 、O_RDWR(只读 只写 读写)
O_APPEND O_APPEND 属性去打开文件时,如果这个文件中本来是有内容的,则新写入的内容会接续到原来内容的后面
O_TRUNC O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,则原来的内容会被丢弃。
O_CREAT O_CREAT 若不存在则创建。open 函数在使用 O_CREAT 标志去创建文件时,可以使用第三个参数 mode 来指定要创建的文件的权限。mode 使用 4 个数字来指定权限的,其中后面三个很重要,对应我们要创建的这个文件的权限标志。譬如一般创建一个可读可写不可执行的文件就用 0666
O_EXCL 加上 O_EXCL 如果文件已经被打开则打开失败
O_NONBLOCK 默认为阻塞模式。添加这个设置为非阻塞模式
O_SYNC write 阻塞等待底层完成写入才返回到应用层。
2.2 read
ssize_t read(int fd, void *buf, size_t count);
2.3 write
ssize_t write(int fd, const void *buf, size_t count);
2.3lseek
执行 read 与 write 函数都会使文件指针移动。lseek 函数可以调整文件指针。
off_t lseek(int fd, off_t offset, int whence);
参数解释。第一个代表文件描述符、第二个代表移动个数、第三个设置移动头。
lseek(fd, 0 ,SEEK_SET);// 文件指针指向头
SEEK_SET 代表指向头
SEEK_END 代表尾部
SEEK_CUR 代表当前位置
3.3close
可以通过 close 关闭打开的文件描述符。
3.4dup 和 dup2 函数
(1)dup 系统调用对 fd 进行复制,会返回一个新的文件描述符(譬如原来的 fd 是 3,返回的就是 4)
(2)dup 系统调用有一个特点,就是自己不能指定复制后得到的 fd 的数字是多少,而是由操作系统内部自动分配的,分配的原则遵守 fd 分配的原则
(3)dup2 和 dup 的作用是一样的,都是复制一个新的文件描述符。但是 dup2 允许用户指定新的文件描述符的数字。
3.5fcntl 的原型和作用
fcntl 函数是一个多功能文件管理的工具箱,接收 2 个参数 + 1 个变参。第一个参数是 fd 表示要操作哪个文件,第二个参数是 cmd 表示要进行哪个命令操作。变参是用来传递参数的,要配合 cmd 来使用。
4、标准 IO 库介绍
4.1、标准 IO 和文件 IO 有什么区别
(1) 看起来使用时都是函数,但是:标准 IO 是 C 库函数,而文件 IO 是 linux 系统的 API
(2)C 语言库函数是由 API 封装而来的。库函数内部也是通过调用 API 来完成操作的,但是库函数因为多了一层封装,所以比 API 要更加好用一些。
(3) 库函数比 API 还有一个优势就是:API 在不同的操作系统之间是不能通用的,但是 C 库函数在不同操作系统中几乎是一样的。所以 C 库函数具有可移植性而 API 不具有可移植性。
(4) 性能上和易用性上看,C 库函数一般要好一些。譬如 IO,文件 IO 是不带缓存的,而标准 IO 是带缓存的,因此标准 IO 比文件 IO 性能要更高。
4.2、常用标准 IO 函数介绍
(1) 常见的标准 IO 库函数有:fopen、fclose、fwrite、fread、ffulsh、fseek
- #include
- #include
- #include
- #include
- #include
- #include <string.h>
- int main(int argc , char *argv[])
- {
- int fd=-1; //fd 文件描述符
- char buf[100]={0};//字符缓冲区
- char writebuf[20]="ilovezw";//字符缓冲区
- int ret;
- //第一步打开
- //fd = open ("a.txt",O_RDWR|O_APPEND);
- fd = open ("a.txt",O_RDWR);
- if(-1 == fd)
- {
- perror("文件打开错误");
- _exit(-1);
- }
- else
- {
- printf("文件打开成功,fd=%d.\n",fd);
- }
- //lseek(fd, 3 ,SEEK_SET);//光标位移3
- //第二步读写
- ret=write(fd,writebuf,strlen(writebuf));//写函数
- if(-1==ret)
- {
- //printf("write error");
- perror("write error");
- _exit(-1);
- }
- else
- {
- printf("ret=%d\n",ret);
- //lseek(fd, -ret ,SEEK_SET);//光标位移3
- }
- lseek(fd, 0 ,SEEK_SET);//文件指针指向头
- ret=read(fd,buf,20);//读文件
- if(-1==ret)
- {
- //printf("read error");
- perror("read error");
- _exit(-1);
- }
- else
- {
- printf("ret=%d,a.txt=%s\n",ret,buf);
- }
- //第三步关闭
- close(fd);
- return 0;
- }
- /*标准IO
- */
- #include
- #include
- #include<string.h>
- int main(int argc,char * argv[])
- {
- FILE *fp =NULL;
- char buf[10];
- fp = fopen("1.txt","r+");
- if(NULL == fp)
- {
- perror("fopen");
- exit(-1);
- }
- memset(buf,0,sizeof(buf));
- fwrite("12345",1,5,fp);
- fseek( fp, 0, SEEK_SET );
- fread(buf,1,5,fp);
- printf("%s\n",buf);
- fclose(fp);
- return 0;
- }
来源: http://www.bubuko.com/infodetail-1984991.html