文件IO学习【三】
系统IO接口说明
概念解释
由于Linux系统下“一切皆文件”,也就是Linux系统下的数据和程序都是以文件的形式存储的,所以Linux内核会提供一组操作文件的函数接口,这组函数接口也被称为系统IO,同时为了满足用户访问文件的需求以及提高用户程序的可移植性,标准库也提供了一组操作文件的函数接口,这组函数接口也被称为标准IO,只不过标准库提供的标准IO函数都是遵循ANSI C标准设计出来,是为了方便用户在不同的操作系统下可以调用通用的函数来实现对文件的读写访问,但其实标准IO也是基于内核提供的系统IO设计出来的。
标准IO和系统IO的区别
-
标准IO提供了输入输出缓冲区,而系统IO不具备输入输出缓冲区。
该特性使得标准IO避免了频繁的系统调用,且不用人为关心缓冲区大小的选择,整体上提高了I/O的效率;而系统IO则无法高效的处理数据。原因是系统调用与普通函数调用相比通常需要花费更多的时间,因为系统调用的过程中内核要执行一系列的操作:首先内核需要捕获调用,然后再检查系统调用传递的参数的有效性,最后在用户空间和内核空间之间传输数据。
-
系统IO能够针对特定类型文件(链接文件、套接字文件等)进行访问,一般适合访问数据需要实时刷新的硬件设备(LCD、触摸屏......);而标准IO做不到这点,标准IO一般适合访问普通文件(规则文件)
简单理解:
标准I/O可以看成是在系统I/O的基础上封装了缓冲机制。这样可以先读写缓冲区,必要时再访问实际文件,从而减少了系统调用的次数,提高访问效率。例如:
fwrite() --> a.txt -->fwrite(buf,1,1,fp); --> write() --> 100个字节要写--> 调用100次write() -->慢
fwrite() --> a.txt -->fwrite(buf,1,100,fp); -->write() -->100个字节要写-->调用001次write() -->快
-
*标准IO利用链表管理打开文件,所以返回值为FILE 而系统IO利用顺序表管理打开文件,所以返回值是一个文件描述符(非负整数),意为顺序表数组的下标。
常用系统IO函数介绍
打开文件
Linux内核提供了用于打开文件的open函数,由于该函数属于系统IO函数,所以在C99标准中是查询不到的,可以在Linux系统的man手册的第二个章节中找到系统IO函数的描述。
通过man手册可知,open()的第一个参数为要打开的文件的路径,第二个参数flags为打开文件的部分权限【只读、只写,可读可写】,且flags必须在这三种权限中选择一个填写。但是,设计者考虑到打开文件情况的多样性,还设定了多个不同的权限函数,使用时需要利用位或(bitwise-or)将多个模式连接,例如: O_RDONLY | O_CREAT
可以看到,open函数的第三个参数mode只有在open函数的第二个参数flags使用O_CREAT或者O_TMPFILE才会使用,也就是说,打开一个已经存在的文件使用第一个版本的open函数即可,第二个版本的open函数的mode参数是指利用open函数创建新文件时给新创建的文件一个指定权限,被创建的文件的权限其实就是Linux系统下文件的权限,可以分为三种:
一般在Linux系统下可以直接使用shell命令来修改文件的权限,比如指令chmod 0777 xxx.txt就是给该文档一个最高权限。注意:mode参数应该采用八进制。
注意:
由于此时mode参数采用八进制表示,所以在使用时应该加上八进制标识符0.
通过man手册可以知道,如果文件打开成功,则open函数返回一个文件描述符,这个文件描述符是一个非负整数,并且这个非负整数是当前进程中未被分配的文件描述符中最小的。如果文件打开失败,则open函数的返回值是-1,并且错误原因采用错误码的方式进行存储。
思考:
请问这个open函数的文件描述符有什么作用?这个返回值对应的数字范围是多少???
回答:
open函数的返回值可以理解为是被打开文件的代号,内核并不是以被打开文件的路径和名称来管理文件,而是在调用open函数的时候会从未分配的文件描述符中找到一个最小的提供给被打开的文件。在对文件进行读写(R/W)访问时同样是通过这个文件描述符实现。所以需要再打开文件时设置一个整型变量用于存储open()返回的文件描述符。
- 文件描述符本质就是一个非负整数,从内核源码角度分析,这个整数实际上是内核中的一个称为 fd_array 的数组下标。
打开文件时,内核产生一个指向 file{} 的指针,并将该指针放入一个位于 file_struct{} 中的数组 fd_array[] 中,而该指针所在数组的下标,就被 open() 返回给用户,所以内核把这个数组下标称为文件描述符。
文件描述符从0开始分配,每打开一个文件,就产生一个新的文件描述符。当然,用户可以重复打开同一个文件,每次打开文件都会使内核产生新的结构体,并得到不同的文件描述符。
注意:
虽然文件描述符是从0开始分配,但由于我们打开文件一般是做读写操作,所以在打开文件之前,系统会打开三个标准文件【stdin、stdout、stderr】。因此,打开文件的描述符最小应该是从 3 开始。
思考:
可以知道一个程序中能打开的文件的数量最大为1024个,请问是否可以修改这个值
回答:
可以通过Linux系统提供了一个查看和修改系统资源的shell命令: ulimit -a 来打印当前系统所有资料的阈值
注意:
上图所示的数值,均是由Linux内核设置,若是想要对其进行修改操作,则需要写明修改项的参数,如想要对打开文件的数量进行修改:ulimit -n xxx(想要修改的数量)
且这样的修改本次有效,若是想要一直有效,则需要将其写入Linux内核代码中。
关闭文件
Linux内核提供了用于关闭文件的close函数,由于该函数属于系统IO函数,所以在C99标准中是查询不到的,可以在Linux系统的man手册的第二个章节中找到系统IO函数的描述。
注意:
close函数可以对同一个文件反复调用,并且不会出错,因为open函数没有申请堆内存,但是多次调用close函数关闭同一个文件的动作是没有意义。
文件读取
Linux内核提供了用于读取文件的read函数,由于该函数属于系统IO函数,所以在C99标准中是查询不到的,可以在Linux系统的man手册的第二个章节中找到系统IO函数的描述。
由man手册可知,read()需要三个参数,分别是读取文件的路径、存放读取数据的自定义缓冲区和需要读取的数量。且与fread()不同的是,read()能够读到最多count个数据,而不是count - 1个;
read()的返回值是读取字节数量,如果返回值是0,说明读取到文件末尾,如果返回值是-1,说明读取出错。
文件写入
Linux内核提供了用于写入文件的write函数,由于该函数属于系统IO函数,所以在C99标准中是查询不到的,可以在Linux系统的man手册的第二个章节中找到系统IO函数的描述。
位置偏移
Linux内核提供了用于设置文件位移的lseek函数,由于该函数属于系统IO函数,所以在C99标准中是查询不到的,可以在Linux系统的man手册的第二个章节中找到系统IO函数的描述。
注意:
lseek()与fseek()使用规则一致,但是,lseek()会将文件指示器相较于文件开头的偏移量,以字节为单位返回。而fseek()需要借助ftell()才能做到。