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 <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 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<stdio.h> #include<stdlib.h> #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; }