Linux基础IO

对文件的理解:

文件 = 文件内容 + 属性(即数据)

linux认为,一切皆文件。

站在系统的角度而言,能够被input读取或者能够被output写出的设备就叫做文件。

狭义上的文件:普通的磁盘文件

广义上的文件:显示器,键盘,网卡,声卡,显卡等几乎所有的外设,都可以称为文件。

当前路径:当一个程序运行起来的时候,每一个进程都会记录自己当前所处的工作路径,所谓当前路径就是指,该进程所处的工作路径。

一、C标准库IO操作

1.1、写文件


#include <stdio.h>
#include <string.h>
int main()
{
	//使用只写的方式打开文件,fp为 FILE* 类型指针
	FILE *fp = fopen("myfile", "w");
	//如果打开失败,打印错误	
	if(!fp){
		printf("fopen error!\n");
 	}
 	const char *msg = "hello bit!\n";
 	int count = 5;
 	while(count--){
	//使用fwirte将msg写入文件,该接口为c标准库接口
 		fwrite(msg, strlen(msg), 1, fp);
 	}
	//写入完成后,关闭文件
 	fclose(fp);
	return 0;
}

1.2、读文件

#include <string.h>
int main()
{	
    //使用只读的方式打开文件,fp为 FILE* 类型指针
	FILE *fp = fopen("myfile", "r");
	if(!fp){
		printf("fopen error!\n");
 	}
	char buf[1024];
 	const char *msg = "hello bit!\n";
	while(1){
 		//注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
 		ssize_t s = fread(buf, 1, strlen(msg), fp);
 		if(s > 0){
 			buf[s] = 0;
 			printf("%s", buf);
 		}
 		if(feof(fp)){
 			break;
 		}
	 }
		fclose(fp);
		rerturn 0;
}

1.3、输出信息到显示器

#include <stdio.h>
#include <string.h>
int main()
{
	const char *msg = "hello fwrite\n";
    
    //使用fwirte将字符串打印到stdout中
	fwrite(msg, strlen(msg), 1, stdout);
    
	printf("hello printf\n");
    
	fprintf(stdout, "hello fprintf\n");
	return 0;
}

1.4、stdin & stdout & stderr

c语言进行运行的时候会默认打开三个输入输出流:stdin ,stdout ,stderr

这三个流的返回值都是FILT* 类型的文件指针

在这里插入图片描述
在这里插入图片描述

1.5、总结

在这里插入图片描述

打开文件的方式如上所示

二、系统文件IO操作

2.1、写入操作

2.1.1、关于open操作的解释

在这里插入图片描述

  1. pathname:要打开的文件的路径名。
  2. flags:打开文件的标志,指定了打开文件的方式。常用的标志包括:
    • O_RDONLY:只读模式,打开文件用于读取。
    • O_WRONLY:只写模式,打开文件用于写入。
    • O_RDWR:读写模式,打开文件用于读取和写入。
    • O_CREAT:如果文件不存在,则创建文件。
    • O_TRUNC:如果文件存在且以写入方式打开,则清空文件内容。
    • O_APPEND:写入时追加到文件末尾。
  3. mode:如果使用了O_CREAT标志,指定新建文件的权限。在大多数情况下,这个参数可以被忽略。
2.1.2、写入代码
#include<stdio.h>
#include<sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
    //umask为权限掩码
	umask(0);
    //open函数参数:第一个为文件路径,第二个参数为打开文件的方式,第三个参数指定文件的权限
	int fd = open("myfile", O_WRONLY|O_CREAT, 0644);
	if(fd < 0){
 		perror("open");
 		return 1;
 	}
 	int count = 5;
 	const char *msg = "hello bit!\n";
 	int len = strlen(msg);
 	while(count--){
 		write(fd, msg, len);//fd: 后面讲, msg:缓冲区首地址, len: 本次读取,期望写		入多少个字节的数据。 返回值:实际写了多少字节数据
 	}
 	close(fd);
 	return 0;
}

2.2、读文件

2.2.1、关于read操作

在这里插入图片描述

  1. fd:文件描述符,指定要读取的文件。
  2. buf:用于存储读取数据的缓冲区的指针。
  3. count:要读取的字节数。
2.2.2、代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
 	int fd = open("myfile", O_RDONLY);
 	if(fd < 0){
 		perror("open");
 		return 1;
 	}
 	const char *msg = "hello bit!\n";
	char buf[1024];
 	while(1){
 		ssize_t s = read(fd, buf, strlen(msg));//类比write
 		if(s > 0)
    	{
 			printf("%s", buf);
 		}
    	else
    	{
 			break;
 		}
 	}
 	close(fd);
 	return 0;
}

2.3、open函数返回值

  • 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
  • 而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

在这里插入图片描述

三、文件描述符(fd)

3.1、fd的引入

在这里插入图片描述

如上图代码,可看到如下结果:

在这里插入图片描述

为什么文件描述符是从3开始的,理论上应该是从0开始,实际上0,1,2已经被stdio,stdout,stderr占用。

在这里插入图片描述

在这里插入图片描述

而这些open,fopen等等的接口的返回值都是一个FILE* 类型的,那么FILE*是什么呢,通过查阅底层代码可看到FILE其实是一个结构体,而C文件,库函数内部一定要调用系统调用,在系统的角度,只认fd,那么就可以知道,在FILE这个结构体中一定封装了fd。

3.2、对fd的理解(本质是数组下标)

什么是fd呢:

​ 一个进程想要访问文件,就需要先打开文件,一般而言,一个进程可以打开多个文件,而文件要被访问的前提是要加载到内存中,只有文件被加载到内存中才可以直接被访问。

​ 系统中存在大量的被打开的文件,所以操作系统(OS)需要通过先描述再组织的方法将这些文件管理起来,在内核中,操作系统内部为了管理一个被打开的文件,构建一个struct file结构体,充当一个被打开的文件,如果文件很多,则会使用双链表来进行连接。

struct file
{
	struct file* next;
	strucr file* prev;
	//包含一个被打开文件的几乎所有的内容。
}

在这里插入图片描述

四、重定向

4.1、输出重定向

在这里插入图片描述

此时运行上述代码可以看到如下结果:

在这里插入图片描述

可以发现,本来应该输出到显示器的内容却输出到了文件log.txt中,这种现象叫做输出重定向,常见的重定向方式有>,>>,<.

在这里插入图片描述

重定向的本质:在操作系统内部,更改fd对应的内容的指向。

4.2、dup2系统调用重定向

4.2.1、dup2介绍

在这里插入图片描述

  1. oldfd:表示需要复制的源文件描述符。这个参数是一个整数值,通常是一个已经打开的文件描述符。
  2. newfd:表示目标文件描述符。这个参数也是一个整数值,通常是另外一个已经打开的文件描述符。如果该文件描述符已经被打开,则会在复制之前关闭它。
4.2.2、dup2使用在这里插入图片描述

在这里插入图片描述

4.3、缓冲区、

***缓冲器就是一段内存空间,这个空间有c标准库维护。***该结构被包含在FILE结构体中。

缓冲区的缓冲策略:

  1. 立即刷新。

  2. 行刷新(行缓冲)

  3. 满刷新(全缓冲)

  4. 特殊情况:用户强制刷新(fflush),进程退出。

    关于缓冲区的认识:

一般而言,行缓冲的设备文件为:显示器,全缓冲的设备文件:磁盘文件。

所有的设备都倾向于全缓冲,因为缓冲器满了才刷新,这样只需要更少次的 IO 操作,更少次的外设访问,能提高效率。

显示器:显示器上的内容是直接给用户看的,一方面需要照顾效率,另一方面也需要照顾用户体验,在极端情况下是可以自定义规则的。

在这里插入图片描述

在该程序中同样的一个程序,向显示器打印输出4行文本,但是向普通文件(磁盘上),打印的时候,变成了7行,其中:

​ 1.C I0接口是打印了2次的

​ 2.系统接口,只打印一次和向显示器打印一样!

​ 如果向显示器打印,刷新策略是行刷新,那么最后执行fork的时候–一定是函数执行完了 &&数据已经被刷新了! fork也就无意义了!

​ 如果你对应的程序进行了重定向 – 要向磁盘文件打印 – 隐形的刷新策略变成了全缓冲! 边没有意义了fork的时候 --一定是函数一定执行完了,但是数据还没有刷新!!-在当前进程对应的C标准库中的缓冲区中!!–这部分数据是父进程的数据

五、文件系统

5.1、背景知识

​ 在磁盘中还有一类文件,这类文件叫做磁盘级文件,这类文件在进行文件操作的时候未被打开。

5.2、了解磁盘

​ 内存:掉电易失存储介质;磁盘:永久性存储介质—SSD ,U盘,flash卡,光盘,磁带等。磁盘是一个机械设备,是操作系统中唯一一个机械设备,是一个外设。

在这里插入图片描述
​ 在物理上,磁盘中分为一个一个的扇区,将数据通过特定方式存储在扇区中,当要找到该数据时候,先找到该数据存储的扇区,然后再在该扇区中寻找想要的数据。那么如何找到该扇区呢。那就需要使用CHS寻址。在哪一个面上对应的就是哪一个磁头,找到磁道,然后找到扇区。对磁盘的管理也就说对一个小分区的管理

5.3、文件系统结构

在这里插入图片描述

​ 磁盘的基本单位是扇区(512字节),但是操作系统和磁盘管理的基本单位是:4KB(8*512byte)。如果文件基本单位太小就会导致多次IO 操作,进而使效率降低;如果操作系统使用和磁盘一样大,当磁盘的基本带下改变,就会到子OS的源代码改变,

文件 = 内容 + 属性

  1. Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相 同的结构组成。政府管理各区的例子
  2. Data block :多个4KB大小的集合,报错的都是特定文件的内容。
  3. 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量, 未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的 时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个 文件系统结构就被破坏了
  4. GDT,Group Descriptor Table:块组描述符,描述块组属性信息。
  5. inod Table:inode是一个大小为128字节的空间,保存的是对应文件的属性,该块组内,所有文件的inode空间的集合,需要标识唯一性,每一个inode块都要有一个inode编号,一般而言,一个inode,一个inode编号。
  6. 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用 ,假设有10000+个blocks,10000+比特位:比特位和特定的block是一一对应的,其中比特位为1,代表该block被占用,否则表示可用!
  7. inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。
  8. i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等
  9. 数据区:存放文件内容

创建一个新文件主要有一下4个操作:

  1. 存储属性 内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。
  2. 存储数据 该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据 复制到300,下一块复制到500,以此类推。
  3. 记录分配情况 文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
  4. 添加文件名到目录

5.4、软硬链接

5.4.1、软链接

软链接有独立的inode,是一个独立的文件,软链接的文件内容是指向文件对应的路径,相当于windows系统下的快捷方式。

ln -s 源文件 目标文件

在这里插入图片描述

如图所示,a.txt就是一个链接到abc.txt的软链接。

5.4.2、硬链接

硬链接没有独立的inode,不是一个独立的文件,创建硬链接只是在指定目录下建立了文件名和指定inode建立了映射关系。

ln 源文件 目标文件

在这里插入图片描述

建立了硬链接之后我们发现图示所表示的数由1 变成了2,这个数就是所谓的硬链接数,当创建硬链接这个数就会加1
在这里插入图片描述

通过观察inode可以发现,硬链接的inode和源文件的inode相同,说明创建硬链接不会创建新的inode。

在这里插入图片描述

通过上图可以看见一个发现一个新目录的硬链接数为2,是因为,该目录下有一个 " . "文件的硬链接,那么这样就可以通过硬链接数来看出该目录下有几层。

六、动静态库

6.1、静态库

ar lib静态库名.a 目标文件.o

静态库的前缀为必须为lib,后缀为.a

6.1.1、静态库的使用
6.1.1.1、直接使用

在这里插入图片描述

-I :文件头文件搜索
-L : 库文件搜索路径
-l : 在特定路径下使用的库(去前缀和后缀)
6.1.1.2、静态库安装

自己写的库为第三方库,所以操作系统不会自动连接

C语言默认静态库路径:ls /lib64/libc.a
头文件默认搜索路径为 /usr/include
库文件默认搜索路径为 /usr/lib64

在这里插入图片描述

安装后使用静态库:gcc main.c -lhello

6.2、动态库

在这里插入图片描述

动态库形成:
	形成的 .o 文件需要添加 -fPIC选项,该选项意思为:形成一个与位置无关的目标二进制文件
	动态库的前缀为lib 后缀为 .so 需要添加一个-shared选项
6.2.1、动态库使用
6.2.1.1、直接使用

在这里插入图片描述

如果只有静态库,那么gcc只能针对该库进行静态链接
如果动态库和静态库同时存在,那么默认使用的是动态库
如果动态库和静态库同时存在,非要使用静态库那么需要添加 -static选项
-static:摒弃默认使用动态库的原则,而是使用动态库的原则

但是该命令生成的可执行文件不可以直接运行,需要在LD_LIBRARY_PATH中添加环境变量

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库文件目录
6.2.1.2、配置文件
在/etc/ld.so.conf.d目录下创建一个.conf配置文件,将库文件目录放到该文件目录中,再使用ldconfig使该文件生效
6.2.1.3、建立软链接

在lib64目录下建立一个软连接

6.3、库存在的意义

站在使用库的角度:库的存在可以大大减少开发的周期,提高软件本身的质量

M-1720530783950)]

如果只有静态库,那么gcc只能针对该库进行静态链接
如果动态库和静态库同时存在,那么默认使用的是动态库
如果动态库和静态库同时存在,非要使用静态库那么需要添加 -static选项
-static:摒弃默认使用动态库的原则,而是使用动态库的原则

但是该命令生成的可执行文件不可以直接运行,需要在LD_LIBRARY_PATH中添加环境变量

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库文件目录
6.2.1.2、配置文件
在/etc/ld.so.conf.d目录下创建一个.conf配置文件,将库文件目录放到该文件目录中,再使用ldconfig使该文件生效
6.2.1.3、建立软链接

在lib64目录下建立一个软连接

6.3、库存在的意义

站在使用库的角度:库的存在可以大大减少开发的周期,提高软件本身的质量

站在写库的角度:简单,代码安全

  • 29
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ღ星ღ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值