前言
Linux系统中有一个重要的概念:一切皆文件,本节主要介绍文件系统以及系统调用(文件操作)。
一、文件系统有哪些?
文件系统主要分为:存储设备文件系统、伪文件系统、虚拟文件系统,
1.1 存储设备文件系统
这种文件系统主要是为了解决如何高效管理存储器空间的问题 在Linux下常用有ext2、ext3和ext4的类型格式,Windows常用的有FAT32、NTFS、exFAT 。
我们主要介绍ext4格式:从ext3改进而来,从2008年结束实验期,进入稳定版。支持1EB的分区,单个文件最大支 持16TB,支持无限的子目录数量。
在Linux下,可以通过如下命令查看系统当前存储设备使用的文件系统:
df -T
,从图中可以看出,主机的硬盘设备为/dev/mmcblk1p2
,它使用的文件系统类型为ext4,挂载点是根目录/
1.2 伪文件系统
Linux内核还提供了procfs、sysfs和devfs等伪文件系统,伪文件系统存在于内存中,通常不占用硬盘空间,它以文件的形式,向用户提供了访问系统内核数据的接口。
procfs是进程文件系统,通常自动挂载在根目录下的/proc文件夹,可以通过
cat /proc/cpuinfo
查看CPU信息,ls /proc
查看proc目录
如图所示/proc
包含了非常多以数字命 名的目录,这些数字就是进程的PID号
sysfs通常会自动挂载在根目录下的
sys
文件夹,提供了一些关于设备、内核模块、文件系统以及内核组件的信息
sysfs文件系统在内核加载驱动时,根据系统上的设备和总线构成导出的分级目录,它是系统上设备的直观反应,每个设备在sysfs下都有唯一的对应目录,用户可以通过具体设备目录下的文件访问设备
1.3 虚拟文件系统
为了使不同的文件系统共存,Linux内核在用户层与具体文件系统之前增加了虚拟文件系统中间层,它对复杂的系统进行抽象化,对用户提供了统一的文件操作接口。无论是ext2/3/4、FAT32、NTFS存储的文件,还是/proc、/sys提供 的信息还是硬件设备,无论内容是在本地还是网络上,都使用一样的open、read、write来访问,使得“一切皆文件”的理念被实现
二、系统调用
系统调用(System Call)是操作系统提供给用户程序调用的一组“特殊”函数接口API,文件操作就是其中一种类型。Linux提供的系统调用包含以下内容:
类别 | 系统调用示例 |
---|---|
进程控制 | fork , clone , exit , setpriority 等创建、中止、设置进程优先级的操作 |
文件系统控制 | open , read , write 等对文件的打开、读取、写入操作 |
系统控制 | reboot , stime , init_module 等重启、调整系统时间、初始化模块的系统操作 |
内存管理 | mlock , mremap 等内存页上锁重、映射虚拟内存操作 |
网络管理 | sethostname , gethostname 设置或获取本主机名操作 |
socket控制 | socket , bind , send 等进行TCP、UDP的网络通讯操作 |
用户管理 | setuid , getuid 等设置或获取用户ID的操作 |
进程间通信 | 包含信号量、管道、共享内存等操作 |
现在通过文件操作的两个代码,来演示使用C标准库与系统调用方式的差异
C标准库:使用fopen创建文件、使用fwrite写入内容,使用fflush确保缓冲区的内容写到文件,然后使用fseek重置文件位置指针,使用fread把文件的内容读出,最后调用fclose关闭文件
#include <stdio.h>
#include <string.h>
//要写入的字符串
const char buf[] = "filesystem_test:Hello World!\n";
//文件描述符
FILE *fp;
char str[100];
int main(void)
{
//创建一个文件
fp = fopen("filesystem_test.txt", "w+");
//正常返回文件指针
//异常返回NULL
if(NULL == fp){
printf("Fail to Open File\n");
return 0;
}
//将buf的内容写入文件
//每次写入1个字节,总长度由strlen给出
fwrite(buf, 1, strlen(buf), fp);
//写入Embedfire
//每次写入1个字节,总长度由strlen给出
fwrite("Embedfire\n", 1, strlen("Embedfire\n"),fp);
//把缓冲区的数据立即写入文件
fflush(fp);
//此时的文件位置指针位于文件的结尾处,使用fseek函数使文件指针回到文件头
fseek(fp, 0, SEEK_SET);
//从文件中读取内容到str中
//每次读取100个字节,读取1次
fread(str, 100, 1, fp);
printf("File content:\n%s \n", str);
fclose(fp);
return 0;
}
系统调用对文件进行操作,常用的有open、write、read、lseek、close
- 先调用了open函数以可读可写的方式打开一个文本文件。open与fopen的返回值功能类似,都是文件描述符,不过open使用非负整数来表示正常,失败时返回-1,而fopen失败时返回NULL
- 创建文件后调用write函数写入了“pwdn”、“lsn”这样的字符串
- 使用read函数读取内容前,先调用lseek函数重置了文件指针至文件开头处读取。与C库文件操作的区别write和read之间不需要使用fflush确保缓冲区的内容并写入,因为系统调用的文件操作是没有缓冲区的。
- 最后关闭文件,释放文件描述符
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
//文件描述符
int fd;
char str[100];
int main(void)
{
//创建一个文件
fd = open("testscript.sh", O_RDWR|O_CREAT|O_TRUNC, S_IRWXU);
//文件描述符fd为非负整数
if(fd < 0){
printf("Fail to Open File\n");
return 0;
}
//写入字符串pwd
write(fd, "pwd\n", strlen("pwd\n"));
//写入字符串ls
write(fd, "ls\n", strlen("ls\n"));
//此时的文件指针位于文件的结尾处,使用lseek函数使文件指针回到文件头
lseek(fd, 0, SEEK_SET);
//从文件中读取100个字节的内容到str中,该函数会返回实际读到的字节数
read(fd, str, 100);
printf("File content:\n%s \n", str);
close(fd);
return 0;
}
这个时候我们需要考虑一个问题,C标准库和系统调用的区别就在于库函数带缓冲区
主要表现如下:
- 系统调用会影响系统的性能。执行系统调用时,Linux需要从用户态切换至内核态,执行完毕再返回用户代码,所以减少系统调用能减少这方面的开销
- 如果写少量数据的话,直接执行write可能会更高效,但如果是频繁的写入操作,由于fwrite的缓冲区可以减少调用write的次数,这种情况下使用fwrite能更节省时间
- 硬件本身会限制系统调用本身每次读写数据块的大小,使用fopen带缓冲区可以尽量满足数据长度要求时才执行系统调度,减少空间开销
- 在需要对硬件进行确定的控制时,我们更倾向于执行系统调用
总结
介绍了文件系统和系统调用中文件操作,对比了C标准库和系统调用。