linux篇第九篇——基础io(系统文件io+文件描述符+重定向+文件系统+软硬链接)(代码片段)

呆呆兽学编程 呆呆兽学编程     2022-12-08     176

关键词:

⭐️这篇博客就要开始聊一聊Linux中基础IO相关知识,IO相信大家都不陌生,我们在C/C++中对文件进行读写的操作,也就是文件IO,这篇博客我也会带大家回顾一下。这篇博客还会介绍系统中的文件IO调用的接口,还有文件系统相关的内容和概念,文件描述符等相关知识的分享。

目录


🌏C语言文件IO介绍

🌲文件操作库函数的简单使用

C语言的专栏中有专门讲到这一块知识,这里会介绍一些,更细节的内容可以参考这篇博客——C语言文件操作

先看一下C语言的两个库函数:

size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );// 写文件
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );// 读文件
  • fwrite: 第一个参数是从buffer获得数据,第二个参数是一次写入多少个字节的大小的数据,第三个参数是最多写几次,第四个参数是写数据到这个流。返回值是代表这次实际写的次数。只适用于文件流。
  • fread: 第一个参数是从读取数据放到这,第二个参数是一次读入多少个字节的大小的数据,第三个参数是最多读几次,第四个参数是从这个流读数据。返回值是代表这次实际读的次数。只适用于文件流。

实例演示:
实例1: 写文件

#include <stdio.h>
#include <string.h>

int main()

	FILE* fp = fopen("log.txt", "w");
	
	if (fp == NULL)
		perror("open file fail");
		exit(-1);
	
	
	const char* msg = "hello world!\\n";
	int count = 5;
	while (count--)
		fwrite(msg, strlen(msg), 1, fp);
	
	fclose(fp);
	return 0

代码运行结果如下:

实例2: 读文件

#include <stdio.h>
#include <string.h>

int main()

	FILE* fp = fopen("log.txt", "r");
	
	if (fp == NULL)
		perror("open file fail");
		exit(-1);
	
	
	char buf[256] = 0;
	int ret = 0;
	while ((ret = fread(buf, 1, 13, fp)))
		printf("%s", buf);
	
	fclose(fp);
	return 0

代码运行结果如下:

🌲stdin&stdout&stderr

Linux下一切皆文件,硬件设备也是被当做文件看待的,也就是说这些硬件设备也是可以通过IO打开的,并且进行读写。那他们是操作的呢?一般来说,C语言程序运行起来,都会默认打开3个流,分别是:

  • stdin: 标准输入流(键盘)
  • stdout: 标准输出流(显示器)
  • stderr: 标准错误流(显示器)

stdin,stdout和stderr背后支撑的硬件设备分别是键盘、显示器和显示器。

#include <stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

仔细观察可以发现,它们都是FILE*类型的,也就是文件指针。所以,我们不需要考虑要打开键盘和屏幕这些流。这也是为什么我在打印数据到屏幕或从键盘上输入数据时,即使我们没有打开这些流,我们也可以执行这些操作的原因。
输出信息到屏幕的几种方式:

  1. 直接使用库函数printf 进行打印
  2. 将文件操作中写文件的库函数传参进行更改,我们选择不传文件,而是传一个stout,因为它的类型也是FILE*,,所以我们可以把数据写到屏幕上
    实例演示:
#include <stdio.h>
#include <string.h>

int main()

	FILE* fp = fopen("log.txt", "w");
	
	if (fp == NULL)
		perror("open file fail");
		exit(-1);
	
	
	const char* msg = "hello world!\\n";
	int count = 5;
	while (count--)
		fwrite(msg, strlen(msg), 1, stdout);// 把fp改成stdout
	
	fclose(fp);
	return 0

代码运行结果如下: 显然,数据是被输出到屏幕上的

总结: C语言会默认打开三个流,而且C++等其它的语言都会有对应的手段,且这些都是由操作系统来进行支持。由不同的语言进行封装。

🌏系统文件IO

操作系统底层其实是提供了文件IO的系统调用接口的,有write,read,close和lseek等一套系统调用接口,不同语言会对这些系统调用接口进行封装,封装成某个语言自己的一套文件IO的库函数,这样在语言层面,程序员只需要语言的调用库函数,无需关系底层的系统调用,降低了开发成本。

🌲系统调用接口的介绍

这里主要介绍open、read、wirte和close

🍯open

作用: 打开一个文件
函数原型:

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode)

参数介绍:

  • pathname: 要打开或创建的目标文件的路径名
  • flags: 打开文件时,可以传入多个参数,用传入的参数进行或运算,得出flags
    选项:
    O_RDONLY: 只读打开
    O_WRONLY: 只写打开
    O_RDWR: 读,写打开这三个常量,必须指定一个且只能指定一个
    O_CREAT: 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
    O_APPEND: 追加写
    说明: 这里的每一个选项都只有一个比特位是为1的,其余都是0,所以将这些选项组合就是对这些选项进行或运算,然后传个flags
  • mode: 文件权限。在新文件被创建时, 参数mode具体 指明了使用权限。他通常也会被umask 修改。所以一般新建文件的权限为 (mode &~umask)。

返回值:

  • 成功:返回新的文件描述符
  • 失败:-1

实例演示: open函数的使用,研究函数返回值

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

int main()

	int fd1 = open("log.txt1", O_RDONLY|O_CREAT, 0664);
	int fd2 = open("log.txt2", O_RDONLY|O_CREAT, 0664);
	int fd3 = open("log.txt3", O_RDONLY|O_CREAT, 0664);
	int fd4 = open("log.txt4", O_RDONLY|O_CREAT, 0664);
	int fd5 = open("log.txt5", O_RDONLY|O_CREAT, 0664);
	
	printf("fd1:%d\\n", fd1);
	printf("fd2:%d\\n", fd2);
	printf("fd3:%d\\n", fd3);
	printf("fd4:%d\\n", fd4);
	printf("fd5:%d\\n", fd5);
	
	return 0;

代码运行结果如下:

观察运行结果可以发现,返回值fd是从3开始分配,且是递增的,不知道大家对这一连串的数字可以联想到什么。
是数组下标吗?对的,fd的本质就是数组的下标,其实这些返回值就是一个数组的下标。那问题又来了,既然是数组下标,那0,1,2去哪了?其实在Linux下,进程会默认把3个文件描述符分配(0,1和2)给标准输入、标准输出和标出错误,所以,后序如果打开文件,文件描述符就是从3开始分配的。

🍯close

作用: 关闭文件
函数原型:

int close(int fd);

函数参数:

  • fd: 文件描述符

🍯write

作用: 写文件
函数原型:

ssize_t write(int fildes, const void *buf, size_t nbyte);

函数参数:

  • fd: 在文件描述符为fd的文件中进行写入
  • buf: 从buf位置开始读取数据
  • nbyte: 从buf位置开始读取nbyte个字节到文件中

函数返回值:

  • 成功:返回实际写入数据的字节数
  • 失败:返回-1

实例演示:

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

int main()

  int fd = open("log.txt", O_WRONLY|O_CREAT, 0664);

  char buf[15] = "hello world\\n";
  write(fd, buf, sizeof(buf)/sizeof(buf[0]));

  close(fd);
  return 0;

代码运行结果如下:

🍯read

作用: 写文件
函数原型:

ssize_t read(int fd, void *buf, size_t count);

函数参数:

  • fd: 在文件描述符为fd的文件中开始读
  • buf: 把读得内容从buf的位置开始存放
  • count: 从buf位置开始存放count个字节

函数返回值:

  • 成功:返回实际读取数据的字节数
  • 失败:返回-1

实例演示:

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

int main()

	int fd = open("log.txt", O_RDONLY|O_CREAT, 0664);
	
	char buf[15] = "hello world\\n";
	read(fd, buf, 15);
	printf("%s", buf);
	close(fd);
	return 0;

代码运行结果如下:

🌏文件描述符fd

fd: 打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。

注意:

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

思考1:一个进程可以打开多个文件,那这些文件是如何管理起来的呢?

答案是先描述,再组织。操作系统在内存中创建相应的数据结构来描述目标文件。也就是用一个struct file的结构体把每个文件描述起来,如下:

其中,为了模拟面向对象中的成员方法,这里通过函数指针来模拟实现,里面用不同的函数指针指向了对应的文件(键盘、显示器或磁盘等等)的操作方法,指向不同的文件对应的硬件设备读写方法,这里就模拟实现了C++中面向对象的多态的一大特性。
在文件系统之上,有一层内核软件层——vfs(Virtual File System)VFS是一个可以让open()、read()、write()等系统调用不用关心底层的存储介质和文件系统类型就可以工作的粘合层。为不同文件系统的通信提供了媒介。在操作系统看来,每个file结构体在文件层面上都是一样的,所以说多态是对一切皆文件更高层次理解的一种表现。
对于进程而言,先找到file_struct,然后找到fd_array的指针数组,通过下标fd找到对应的file*,从而找到对应的文件,然后struct file中的函数指针就可以实现对文件的读写操作。
如何组织?
用一个双链表的结构把打开的文件链接起来,文件的打开和关闭就是对双链表的增删查改。

思考2:进程如何与文件关联起来?

每个进程中都有一个struct file_struct* 的结构体指针,指向的是一个file_struct的结构体,这个结构体里面有一个sturct file* 的指针数组fd_array[],里面指向的就是一个一个的struct file,如下图:

进程如何找到对应文件?(进程和文件关联)
进程的task_struct中可以找到一个叫struct file*的指针,这个指针指向file_struct这张表,这样表里面又有一个指针数组,指向的是每一个文件的结构体,通过fd (数组下标)可以找到对应的struct file,这个结构体里面就包含文件属性和相关的inode元信息,还有对文件进行读写操作的一些方法。这样就达到通过fd找到对应文件的目的。

🌏文件描述符

在open的使用那块我已经演示了fd的一些分配规则,也就是默认从3开始分配,因为Linux进程默认情况下会打开3个缺省的文件描述符,上面介绍过了。所以我们的进程在打开文件时,就是从3开始配。
下面做一个小实验: 关闭fd为0的文件,也就是标准输入,此时我们打开两个文件,看看这两个fd分别是多少

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

int main()

    close(0);
	int fd1 = open("log.txt1", O_WRONLY|O_CREAT, 0664);
	int fd2 = open("log.txt2", O_WRONLY|O_CREAT, 0664);
	
	printf("fd1:%d\\n", fd1);
	printf("fd2:%d\\n", fd2);

	close(fd1);
	close(fd2)	
	return 0;

代码运行结果如下:

观察实验结果,可以发现,关闭了fd为0的文件后,后序打开的文件,文件描述符就是从0开始分配,然后分配没有被使用的fd,也就是3。从最小未被使用的文件描述符开始分配。
文件描述符分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符

🌏重定向

🌲概念

概念: 重定向是指修改原来默认的一些东西,对原来系统命令的默认执行方式进行改变

重定向一般有以下几种:

  • 输出重定向: >
  • 输入重定向: <
  • 追加重定向: >>

看下面的演示:

正常使用echo命令,字符串是输出在显示器上的,加了输出重定向后,字符串被输出到文件上了。也就是把本应该打印到显示器上的内容打印到了文件上了。输出重定向改变了默认的输出方式。

🌲原理

🍯输出重定向

实例演示: 关闭标准输出流,也就是fd为1的文件,此时我们再以写的方式打开一个文件,然后进行输出,观察现象

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()

	close(1);
	int fd = open("log.txt", O_WRONLY|O_CREAT, 00644);
	if(fd < 0)
		perror("open");
		return 1;
	
	printf("fd: %d, you can see me...\\n", fd);
	fflush(stdout);
	close(fd);

	return 0

运行结果如下:

观察实验结果,可以发现关闭标准输出流后,本应该打印到显示器上的字符串被输出到了log.txt 的文件中。
从上述实验现象分析重定向原理: 关闭了标准输出流,再以写的方式打开一个文件(被分配fd=1),凡是要在fd=1写的内容,现在都写到了log.txt中,如下图:

🍯追加重定向

实例演示: 关闭标准输出流,也就是fd为1的文件,此时我们再以追加(O_APPEND)的方式打开一个文件,然后进行输出,观察现象

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()

	close(1);
	int fd = open("log.txt", O_WRONLY|O_APPEND|O_CREAT, 00644);
	if(fd < 0)
		perror("open");
		return 1;
	
	printf("fd: %d, you can see me...\\n", fd);
	fflush(stdout);
	close(fd);

	return 0

运行结果如下:

根据输出重定向的原理,我们也不难介绍这个现象,关闭了标准输出流,我们以追加的方式打开一个文件,这个文件被分配了一个为1的文件描述符。因为printf是库函数,是往fd为1的文件进行输出,所以这里也是直接在log.txt文末进行追加

🍯输入重定向

实例演示: 关闭标准输入流,以读的方式打来文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()

	close(0);
	int fd = open("log.txt", O_RDONLY);
	if(fd < 0)
		perror("open");
		return 1;
	
	char buf[256] = 0;
	scanf("%s", buf);
	printf("%s\\n", buf);
	close(fd);
	return 0

运行结果如下:

分析原理:

🌲dup2系统调用

作用: 复制文件描述符给一个新的文件描述符,让fd_array数组中下标为oldfd的内容拷贝给下标为newfd的内容,也就是让newfd的指向发生改变,指向oldfd所指向的文件

函数原型:

int dup2(int oldfd, int newfd);

参数介绍:

  • oldfd: 要复制的文件的文件描述符
  • newfd: 让文件描述符文newfd的文件称为文件描述符为oldfd的文件的一份拷贝

实例演示: 用系统调用dup2来实现输出重定向

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() 
	int fd = open("log.txt", O_CREAT | O_WRONLY);
	if (fd < 0) 
		perror("open");
		return 1;
	
	dup2(fd, 1);
	printf("fd:%d, you can see me\\n", fd);
	close(fd);
	return 0;

代码运行结果如下:

dup2原理分析: printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了log.txt的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入

🌏FILE

概念: FILE是C语言的一个对文件进行描述的一个结构体。因为IO相关的函数与系统调用接口是对应的,且库函数封装了系统调用,所以本质上访问文件都是通过fd(fd_array数组下标)进行访问的,所以C语言中的FILE结构体内部,必定封装了fd。

我们可以在 /usr/include/libo.h 打开文件,查看FILE 结构体

typedef struct _IO_FILE FILE;


完整内容如下:

struct _IO_FILE 
	int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
	查看详情  

实践课-------(第九篇)

通过这十天的框架搭建学习,学会了环境配置,及其测试,得出以下总结1.创建WEB工程添加struts支持2.分包3添加spring支持4.添加spring配置文件5.在web.xml文件中配置初始化读取参数(spring的配置文件)6.配置spring监听... 查看详情

(第九篇)iptables详解

常见的网络攻击形式1.拒绝服务攻击:DOS2.分布式拒绝服务攻击DDOS3.漏洞入侵4.口令猜测以上内容简单了解,具体可自行百度,此处不必知晓。Linux防火墙基础Linux防火墙体系主要工作在网络层,针对TCP/IP数据包实施过滤和限制,... 查看详情

第九篇:使用lstat函数获取文件信息

前言    在之前的文章中,描述过如何用fcntl函数改变文件的状态标记。但,文件还有很多信息,如文件类型,权限设置,设备编号,访问时间等等。如果要获取这些信息,则使用函数 lstat 可以轻松达到这... 查看详情

第九篇数据表设计和保存item到json文件

上节说到Pipeline会拦截item,根据设置的优先级,item会依次经过这些Pipeline,所以可以通过Pipeline来保存文件到json、数据库等等。下面是自定义json#存储item到json文件classJsonWithEncodingPipeline(object):def__init__(self):#使用codecs模块来打开... 查看详情

linux下的基础io(代码片段)

目录一.C语言文件IO操作(库函数)1.打开文件fopen 2.写入二进制文件fwrite和读取二进制文件fread3.关闭文件fclose 二.系统文件IO(系统调用接口)1.打开文件open系统调用2.读文件read和写文件write3.关闭文件close  三.文件描述符1.系统管理... 查看详情

第九篇:网络编程补充与进程

本篇内容udp协议套接字开启进程的方式多进程实现并发的套接字通信join方法守护进程同步锁进程队列生产者消费者模型进程池paramiko模块 一、 udp协议套接字1.TCP和UDP在传输层区别:UDP是无连接不可靠的数据报协议。TCP提... 查看详情

linux基础io(代码片段)

目录系统文件IOopenwritereadclose文件描述符fdFILE重定向使用dup2系统调用文件系统磁盘磁盘和内存交互磁盘的分区与格式化Ext2文件系统的存储方案inode软硬链接软连接硬链接文件的三个时间系统文件IOopenopen接口的作用是打开文件。... 查看详情

linux基础io(代码片段)

目录系统文件IOopenwritereadclose文件描述符fdFILE重定向使用dup2系统调用文件系统磁盘磁盘和内存交互磁盘的分区与格式化Ext2文件系统的存储方案inode软硬链接软连接硬链接文件的三个时间系统文件IOopenopen接口的作用是打开文件。... 查看详情

linux入门基础io(代码片段)

基础IO✔回顾C文件的接口✔系统文件I/O✔文件描述符文件描述符的分配规则重定向✔FILE缓冲区fclose和close✔dup2系统调用✔理解文件系统inode硬链接软链接文件的三个时间✔回顾C文件的接口在学习C语言时我们了解了一些C语言的对... 查看详情

linux入门基础io(代码片段)

基础IO✔回顾C文件的接口✔系统文件I/O✔文件描述符文件描述符的分配规则重定向✔FILE缓冲区fclose和close✔dup2系统调用✔理解文件系统inode硬链接软链接文件的三个时间✔回顾C文件的接口在学习C语言时我们了解了一些C语言的对... 查看详情

linux基础io(代码片段)

文章目录一.C文件IO相关操作二.系统文件IOopen函数返回值重定向使用dup2系统调用给简易shell中增加重定向功能理解文件系统理解软硬链接三.动态库和静态库制作动静态库一.C文件IO相关操作只有文件名但不带路径的话,默认在... 查看详情

linux基础io(代码片段)

文章目录一.C文件IO相关操作二.系统文件IOopen函数返回值重定向使用dup2系统调用给简易shell中增加重定向功能理解文件系统理解软硬链接三.动态库和静态库制作动静态库一.C文件IO相关操作只有文件名但不带路径的话,默认在... 查看详情

linux基础io-io接口,文件描述符,重定向(代码片段)

【Linux】基础IO文章目录【Linux】基础IO一、C语言中文件IO操作1.C语言中的开关读写文件1.1.fopen()1.2.fclose()1.3.fwrite()1.4.fread()2.stdin&&stdout&&stderr3.三个标准流和IO接口二、系统文件IO1.系统级别的开关读写文件1.1.open()1.2.close... 查看详情

linux基础入门--io重定向及管道

IO重定向及管道  一直都提到,程序:指令+数据 其实程序也有IO,数据的来源有多个地方:文件、外部可用于输入的设备:文件(linux一切皆文件)  键盘设备、文件系统上的常规文件加载内容、网卡等;可用于... 查看详情

linux文件系统与基础io(代码片段)

文件的宏观理解1⃣️文件在哪里呢?“狭义”上在磁盘,“广义”上一切皆文件。主要研究“狭义”,磁盘为外设,那么对文件的所有操作,本质都是对外设的输入输出,简称IO2⃣️文件是什么?空文... 查看详情

《linux从0到99》九基础io(代码片段)

基础IO1.回顾c语言文件操作接口2.系统调用文件操作系统01open函数02read函数03write函数04lseek函数05close函数3.文件描述符0&1&2文件描述符的分配规则(最小未分配原则)4.文件描述符与文件流指针的区别01文件流指针的本... 查看详情

第九篇商城系统-商城首页功能(代码片段)

一、商品上架功能ElasticSearch实现商城系统中全文检索的流程。1.商品ES模型商品的映射关系PUTproduct "mappings": "properties": "skuId": "type":"long" , "spuId": "type":&# 查看详情