文件相关概念
1、linux文件结构
在 linux 中,内核会根据不同的设备类型,封装出对应的不同的文件类型,以便顶层的应用进程快速识别底层中的设备,主要分为以下几种:
内核通过文件描述符来进行区分和引用特定的文件
文件描述符
文件描述符是一个简单的整数,它是一个索引值,并指向内核中每个进程打开文件的记录表。当顶层的应用进程需要对底层的设备进行操作时,需要通过内核将设备对应的文件打开,同时内核会将打开的文件封装成文件描述符返回给用户进程。如果应用进程需要对对应的文件进行读写操作时,就会将文件描述符作为参传递给内核函数,进行操作。
一般情况下,当启动一个进程时,内核会自动为这个进程打开三个文件
分别为:
- 标准输入(键盘),对应的文件描述符为 0,即宏 stdin
- 标准输出(屏幕),对应的文件描述符为 1,即宏 stdout
- 标准出错处理(屏幕),对应的文件描述符为 2,即宏 stder
(如果后面进程需要操作新的设备或者文件,则需要通过内核提供的功能函数先打开文件并返回文件描述符,才能进行各种功能操作,而这个文件描述总是从 3 开始递增分配,一个进程最多只能打开 1024 个。)
其中,进程是一个操作主体,文件是一个电路。
示例一:
两个进程打开相同的文件,得到的是相同的文件描述符。
结果:
示例二:
一个进程打开两个不同的文件会得到两个不同的1文件描述符
结果:
2、系统调用
用户空间是应用程序启动后执行时所处的地址空间。
内核空间是操作系统及其服务执行时所处的地址空间。
在有些情况下,用户空间进程需要获得一定的系统服务(调用内核的函数),这时操作系统需要提供一组封装好的“特殊接口”,以便用户进程通过这组接口,进入内核空
间访问内核函数,这时程序运行空间需要从用户空间进入内核空间,处理完之后再返回用户空间。
系统调用的函数会用一组系统调用号来区别。
例子:
int syscall(int number, ...) number 是需要的函数的系统调用号
3、功能函数库
在 linux 操作中,特别是输入输出操作中,直接使用系统调用的效率非常低。
主要是原因是:
1)系统开销大
因为在执行系统调用的时,Linux 必须从用户态转换到内核态,还要返回用户态。
2)硬件对底层系统调用一次所能读写的数据块做出的限制
所以需要功能函数库配合系统调用来使用,因此以下图示的效率比较低。
应用进程在调用系统调用之前,通过调用一组已经实现的用户编程接口程序,对数据及操作进行加工处理,再调用内核提供的系统调用,可以在某种程序上避免出现以上问题
这个用户编程接口即应用空间实的功能函数封装库,例如 libc 库中,就实现了输入输出操作中的 open,read,write 等常用的库函数。通过这些封装好的库函数,我们可以更加快速、便捷地使用内核提供的系统调用。
下面是write系统调用过程分析图:
过程分析:
- 当我们需要向文件写入的时候,调用write函数
- write函数是实现的用户编程接口程序,对数据及操作进行加工处理。
- 接着,write函数会调用内核提供的系统调用syscall(int number, …),指明一个系统调用号。
- 然后这个系统调用号会与sys_write函数绑定,调用相关的驱动代码
- 驱动代码会处理底层的磁盘扇区(也就是当前想要处理的文件)
这两条语句是一样的。
非标准功能函数库的缺点:
但是此功能函数库与系统的关系还是比较大,与系统库是直接绑定的,与这个操作系统相关,只是一个简单的关系转换,这段代码就只能在固定这个系统的里面跑,就不能在其他的系统里面跑,没有跨平台性,会出现系统不兼容的问题,迁移到其他的系统就不能跑起来了。
用的库越高层,就有更好的兼容性。
4、文件的IO操作
有两套大类的操作方式:
1)不带缓存的文件 IO 操作(系统 IO 操作) — 调用非标准库函数实现的 IO 操作
不带缓存的 IO 操作,是通过调用 open、read、write、close 等 libc 库函数间接调用系统调用来实现 IO 操作。因为它们位于 C 标准库的 I/O 缓冲区的底层,所以称为无缓冲 I/O(Unbuffered I/O)。
这种操作方式,可以高效完成文件输入输出。它以文件标识符(整型)作为文件唯一性的判断依据。但是由于这种操作不是 ASCI 标准的,与系统有关,所以移植有一定的问题。当然,效率也是有问题的。
常用的非标准库实现的 IO 操作,主要有以下函数:Open/create/Close/Read/write/Lseek/
2)带缓存的文件 IO 操作 — 通过标准的 LIBC 库实现 IO 操作
带缓存的 IO 操作是在不带缓存的基础之上封装了一层,维护了一个输入输出缓冲区,使之能跨 OS,成为 ASCI 标准,称为标准 IO 库。
由于缓冲区的存在,使得不用频繁地调用系统调用,减少系统的开销,但是响应速度不够及时。移植性较好。
c 库中,常用的 IO 操作函数有 fopen,fwrite,fread 等等。