【Linux】文件系统调用接口

共识原理

  1. 文件 = 内容 + 属性
  2. 文件分为打开的文件 和 没有打开的文件
  3. 文件是进程打开的,研究打开的文件就需要研究进程和文件的关系
  4. 没有打开的文件,需要了解如何存储,文件在存储介质就比如磁盘上保存,而由于没有打开的文件非常多,则需要了解文件时如何被分门别类的存储好的——其本质就是需要快速的找到某一个文件,并进行 增删查改

文件打开

文件被打开,其(内容和属性)必须先被加载到内存,且一个进程可以打开多个文件,内存中存在多个进程,所以在操作系统内部一定存在着大量被打开的文件。

操作系统需要管理大量这些被打开的文件,需要:


先描述再组织


【注意】在内核中,一个被打开的文件都必须有自己的文件打开对象,包含文件的很多属性。

C文件接口

打开与关闭文件

fopen函数打开文件时,如果文件不存在,会创建该文件;打开文件的路径和文件名,默认在当前路径下新建一个文件。

C语言中打开和关闭文件使用fopen与fclose接口

  • FILE* fopen(const char *pathname, const char *mode);
  • int fclose(FILE *stream);

fopen 默认只写文件名称,会在当前路径创建/打开文化,当前路径表示的是进程的当前路径。

int main()
{
	printf("pid : %d\n", getpid());
	// 打开文件,如果文件不存在,则创建一个文件
	// 打开文件的路径和文件名,默认在当前路径下新建一个文件
	FILE* fp = fopen("log.txt", "w");
	if(fp == NULL)
	{
		perror("FILE");
		return 1;
	}

	// 关闭文件
	fclose(fp);


	return 0;
}

fopen打开默认路径在进程的当前路径cwd路径下默认打开——如果修改了当前路径cwd,就会将文件新建在其他目录。

int main()
{
    // 该进程修改路径到根目录
	chdir("/home/dabai");
	printf("pid : %d\n", getpid());
	// 打开文件,如果文件不存在,则创建一个文件
	// 打开文件的路径和文件名,默认在当前路径下新建一个文件
	FILE* fp = fopen("log.txt", "w");
	if(fp == NULL)
	{
		perror("FILE");
		return 1;
	}

	// 关闭文件
	fclose(fp);


	return 0;
}

打开文件的方式:

rOpen text file for reading.  The stream is positioned at the beginning of the file.(只读)
r+ Open for reading and writing.  The stream is positioned at the beginning of the file.(读写)
wTruncate file to zero length or create text file for writing.  The stream is positioned at the beginning of the file.(如果文件不存在,就创建文件来写,如果存在,将文件的长度清空,并从文件开始写
w+Open  for  reading  and writing.  The file is created if it does not exist, otherwise it is truncated.  The stream is positioned at the beginning of the file.
aOpen for appending (writing at end of file).  The file is created if it does not exist.  The stream is positioned at the end of the file.(文件不存在创建文件,在文件的末尾开始写)
a+Open for reading and appending (writing at end of file).  The file is created if it does not exist.  Output is always appended to the end of  the  file.POSIX  is silent on what the initial read position is when using this mode.  For glibc, the initial file position for reading is at the beginning of the file, but for Android/BSD/MacOS, the initial file position for reading is at the end of the file.
  • Linux中在使用echo指令的时候,会使用到 > 输出重定向,而输出重定向的使用结果与w选项相同,都是将文件进行清空,并重新在文件的开始处写入。>log.txt也会将文件进行清空。
  • 在使用echo指令的时候,会使用到 >> 追加重定向,追加重定向的使用结果与a选项相同,都会在文件的末尾写入。

写入文件的方式(1):fwrite

写入文件的方式(2):fread

写入文件的方式(3):fgets

写入文件的方式(4):fscanf

写入文件的方式(4):fprintf

判断文件结尾:feof

下面主要以fwrite举例:

以”w“方式写:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>

int main()
{

	// 打开文件,如果文件不存在,则创建一个文件
	// 打开文件的路径和文件名,默认在当前路径下新建一个文件
	FILE* fp = fopen("log.txt", "w");
	if(fp == NULL)
	{
		perror("FILE");
		return 1;
	
	}

	const char *message = "hello Linux message";
	// 向文件写入
	// strlen(message)不需要加1
	fwrite(message, strlen(message), 1, fp);
	

	// 关闭文件
	fclose(fp);

	return 0;
}

这里strlen中不需要加1,会将"\0"写入文件中,导致乱码。

【注意】字符串以”\0“结尾,是C语言的规定,与文件没有关系。

以"a"方式写:

int main()
{
	// 打开文件,如果文件不存在,则创建一个文件
	// 打开文件的路径和文件名,默认在当前路径下新建一个文件
	FILE* fp = fopen("log.txt", "a");// 以a模式
	if(fp == NULL)
	{
		perror("FILE");
		return 1;
	
	}

	const char *message = "hello Linux message\n";
	// 向文件写入
	fwrite(message, strlen(message), 1, fp);
	
	// 关闭文件
	fclose(fp);

	return 0;
}

"a"以追加的方式写入。

【注意】"w"和"a"都是写入,w会清空文件并在文件开头写,a会在文件结尾追加写。

        Linux下一切皆文件,C语言会打开三个默认输入输出流stdin\stdout\stderr,C++会默认打开三个输入输出流cin\cout\cerr,这三个流也是文件,可以向里面写入。

向文件中写入,或者向显示器文件中写入是没有区别的。

【总结】C程序默认在启动的时候,会打开三个标准输入输出(文件):stdin、stdout、stderr。由于Linux下一切皆文件,所以stdin代表键盘文件,stdout代表显示器文件,stderr代表显示器文件。

文件系统调用

文件是在磁盘上的,磁盘是外部设备,访问文件本质就是访问硬件!无论是什么文件(向显示器打印,显示器也是一个文件)。

几乎所有的库,只要是需要访问硬件设备,就必须封装系统调用。例如:printf/fprintf/fscanf/fwrite/fread/fgets/gets...这些函数都是库函数,其底层一定是封装了系统调用接口。

系统调用接口

打开文件

pathname:要打开或创建的目标文件

flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行"或"运算,构成flags。

参数:

  • O_RDONLY:只读打开
  • O_WRONLY:只写打开
  • O_RDWR:读,写打开

【注意】这三个常量,必须指定一个且只能指定一个

  • O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限。
  • O_APPEND:追加写。

返回值:

        成功:新打开的文件描述符

        失败:-1

认识open系统调用的参数
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>


int main()
{
	// O_WRONLY 表示以写方式打开文件
	// O_CREAT 表示文件如果不存在,创建文件
	int fd = open("log.txt", O_WRONLY | O_CREAT);
	if(fd < 0)
	{
		printf("open file error\n");
		return 1;
	}
		
	return 0;
}

以这种方式创建的文件,权限完全是不对。这是由于Linux下系统调用创建文件的时候,需要明确说明权限。

int main()
{
	// O_WRONLY 表示以写方式打开文件
	// O_CREAT 表示文件如果不存在,创建文件
	// 0666 表示8进制权限
	int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
	if(fd < 0)
	{
		printf("open file error\n");
		return 1;
	}
		
	return 0;
}

通过这种方式设置权限,我们设置的文件权限是0666,但是设置出来的文件却是0664,这是因为Linux中默认存在umask,如果一定需要设置0666权限,可以在局部域中设置umask(0)重新设置掩码。

int main()
{
	umask(0); // 设置掩码,仅仅在这个作用域内有效
	// O_WRONLY 表示以写方式打开文件
	// O_CREAT 表示文件如果不存在,创建文件
	// 0666 表示8进制权限
	int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
	if(fd < 0)
	{
		printf("open file error\n");
		return 1;
	}
		
	return 0;
}

bit位级别方式的标志位传递方式

O_RDONLY、O_WRNLY、O_RFWR、O_RDWR、O_CREAT、O_APPEND这些flags可选项都是宏。flags参数有32个bit位,而这些宏都是某一个bit位是1,所以传递flags参数时候可以传递多个选项。

#define ONE (1<<0) // 1
#define TWO (1<<1) // 2
#define FOUR (1<<2) // 4
#define EIGHT (1<<3) // 8


void show(int flags)
{	
	if(flags & ONE) printf("function 1\n");
	if(flags & TWO) printf("function 2\n");
	if(flags & FOUR) printf("function 4\n");
	if(flags & EIGHT) printf("function 8\n");
}

int main()
{
	printf("---------------------------\n");
	show(ONE);
	printf("---------------------------\n");
	show(TWO);
	printf("---------------------------\n");
	show(ONE | TWO);
	printf("---------------------------\n");
	show(TWO | FOUR);
	printf("---------------------------\n");
	show(ONE | TWO | FOUR | EIGHT);
	printf("---------------------------\n");
	
	return 0;
}

通过位图的方法,可以通过一个参数传递多种选项。


关闭文件

close是系统调用的接口,用于关闭文件。

文件写入

int main()
{
	umask(0); // 设置掩码,仅仅在这个作用域内有效
	// O_WRONLY 表示以写方式打开文件
	// O_CREAT 表示文件如果不存在,创建文件
	// 0666 表示8进制权限
	int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
	if(fd < 0)
	{
		printf("open file error\n");
		return 1;
	}

	const char *message = "hello file system call\n";
	write(fd, message, strlen(message));

	close(fd);
		
	return 0;
}

使用系统调用接口write会对文件进行覆盖写入。

如何做到与fwrite的w选项相同

  • 我们知道fwrite的w选项会将文件先清空,然后再在文件开头处进行写内容。
  • 而系统调用接口仅仅只会在文件的开头处对文件内容进行覆盖式写入。
  • open打开文件的时候,flags 参数可以携带O_TRUNC选项,代表打开文件时清空文件夹。
int main()
{
	umask(0); // 设置掩码,仅仅在这个作用域内有效
	// O_WRONLY 表示以写方式打开文件
	// O_CREAT 表示文件如果不存在,创建文件
	// O_TRUNC 表示打开文件时,清空文件
	// 0666 表示8进制权限
	int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	if(fd < 0)
	{
		printf("open file error\n");
		return 1;
	}

	const char *message = "bbbb";
	write(fd, message, strlen(message));

	close(fd);
		
	return 0;
}

如何做到与fwrite的a项相同

  • 我们知道fwrite的a选项会在文件的末尾处开始撰写。
  • 而系统调用接口仅仅只会在文件的开头处对文件内容进行覆盖式写入。
  • open打开文件的时候,flags 参数可以携带O_APPEND选项,代表打开文件时以追加方式打开
int main()
{
	umask(0); // 设置掩码,仅仅在这个作用域内有效
	// O_WRONLY 表示以写方式打开文件
	// O_CREAT 表示文件如果不存在,创建文件
	// O_TRUNC 表示打开文件时,清空文件
	// O_APPEND 表示以追加方式写入文件
	// 0666 表示8进制权限
	int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);

	if(fd < 0)
	{
		printf("open file error\n");
		return 1;
	}

	const char *message = "bbbb";
	write(fd, message, strlen(message));

	close(fd);
		
	return 0;
}

不管是C语言或者是C++语言,在Linux环境中底层都是使用的同一种接口。

访问文件的本质

  • 每一个文件被打开都会变成操作系统内的一个struct file结构体
  • struct file中会直接或者间接包含:磁盘位置、基本属性、权限、大小、读写位置、用户信息、文件的内核缓冲区信息、struct file* next的指针

  • 进程可以打开多个文件
  • 在进程PCB中存在struct files_struct *files;的指针,表示进程会有file的指针,会指向files_struct的结构体,files_struct的结构体中存有一个指针数组

  • open在磁盘中打开一个文件,该进程调用open后,该进程会在文件描述符表中找到一个没有被使用的下标,将这个下标里面填入打开文件的地址,而返回值正是该地址的下标。int fd就是下标。
  • write写入的时候第一个参数就是文件地址在文件描述符中的下标。
  • close的参数也是文件描述符中的下标。
  • open的返回值本质是数组的下标。
  • 进程和文件管理之间的联系是通过文件描述符的下标。


问题:该进程打开第一个文件默认是3,而如果打开文件失败返回的是-1。那么文件描述符的0, 1 , 2下标的文件在哪呢?

【答】:Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2。而0,1,2对应的物理设备一般是:键盘,显示器,显示器。

  • 在电脑开机的时候,显示器和键盘都已经被打开。所以每一个进程都需要默认打开这三个文件。
  • C文件的接口返回的FILE* 结构体,而这个结构体是C库结构体,这个结构体中一定会保存文件描述符。
  • C++中的流是一个类,类中也一定直接或者间接保存文件描述符。

关闭文件会将文件描述符中的某个下标制空,将struct file结构中的某一个打开的文件中的引用计数-1。

【总结】文件描述符就是从0开始的小整数。当打开文件的时候,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。当进程执行open系统调用,必须让进程和文件关联起来。每一个进程都有一个指针*files,指向一张表files_struct,该表最重要的部分就是包含了一个指针数组,每个元素都是一个指向打开文件的指针。所以,本质上文件描述符就是该数组的下标。所以,只需要拿着文件描述符,就可以找到对应的文件。

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值