Bootstrap

【Linux第七课--基础IO】内存级文件、重定向、缓冲区、文件系统、动态库静态库

引入

a、文件 = 内容 + 属性
b、访问文件之前,都得先打开。修改文件,都是通过执行代码的方式完成修改,文件必须被加载到内存中
c、进程在打开文件
d、一个打开多少个文件?可以打开多个

一定时间段内,系统中有多个进程,那么就有更多同时被进程打开的文件,OS要不要管理多个被进程打开的文件?肯定的,先描述再组织

e、进程和文件的关系,struct task_struct和struct xxxx
f、没有被打开的文件在哪?磁盘

内存级文件

重新使用C文件接口 – 对比重定向

写文件

文件创建的路径和进程有着很大的关系
在这里插入图片描述

tu
像文件里面写入内容:fputs
在这里插入图片描述
在这里插入图片描述

#include<stdio.h>

int main()
{
	FILE* fp = fopen("./log.txt", "w");
	if(fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	
	const char* str = "hello linux\n";
	fputs(str, fp);

	fclose(fp);
	return 0;
}

文件打开再关闭就被清空了?
以w方式打开文件,该文件会被清空在这里插入图片描述
echo “xxxxxx” > log.txt
以a的方式打开,不会清空文件
在这里插入图片描述
echo “xxxxxx” >> log.txt

fwrite写入数据
第一个参数:写入文件的其实内存;第二个参数:写入数据的大小,每个基本单位的大小;第三个参数:写入几个基本单元;
返回值:写入了多少个基本单位

fputs:是写入字符串的形式,它是认识字符串,以\0结尾
fwrite:是以二进制流写入,不认识字符串

在这里插入图片描述
如果没有此文件,当指定绝对路径就会在绝对路径下创建这个文件,否则就在该进程所在目录下创建

int main()
{
	
	FILE* fp = fopen("log.txt", "w");
	if(fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	char* str = "hello file\n";
	int cnt = 5;
	while(cnt)
	{
		int num = fwrite(str, strlen(str), 1, fp);
		cnt--;
	}

	fclose(fp);
	return 0;
}

那改变进程的路径,创建的这个文件也会变
在这里插入图片描述

读文件

fgets:一行读出

文件流

程序默认打开的文件流

stdin标准输入键盘设备
stdout标准输出显示器设备
stderro标准错误显示器设备

向显示器继续打印的方法
printf、fprintf、fwrite、fputs

int main()
{
	FILE* fp = fopen("./log.txt", "w");
	if(fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//标准写入
	//默认想显示器写入,写入流是stdout
	printf("hello printf\n");
	//与printf略有不同,写入到哪里不是默认的
	fprintf(stdout, "hello fprintf\n");
	fprintf(fp, "hello fprintf\n");
	//
	char* str = "hello fputs";
	fputs(str, fp);
	fputs(str, stdout);
	//
	char* str2 = "hello fwrite";
	fwrite(str2, strlen(str2), 1, fp);
	fwrite(str2, strlen(str2), 1, stdout);

	fclose(fp);

标准输入的方法
scanf 、 fread、fscanf

	FILE* fp2 = fopen("./log.txt", "r");
	if(fp2 == NULL)
	{
		perror("fopen");
		return 1;
	}
	char str3[64];
	scanf("%s", str3);
	printf("%s\n", str3);

	fscanf(stdin, "%s", str3);
	printf("%s\n", str3);

	fread(str3, strlen(str3), 1, stdin);
	fclose(fp2);

结论:stdin、stdout、stderro可以直接被使用

认识文件操作的系统接口

访问文件不仅仅有C语言上的文件接口,OS必须提供对应的访问文件的系统调用

w:会清空文件
a:追加文件
r:读取文件

open

在这里插入图片描述

返回值:成功–文件描述符, 失败—1
参数:flags
返回值:int类型的数据

参数 – flag

flag的内容

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

O_RDONY只读打开
O_WRONLY只写打开
O_RDWR读写打开
O_CREAT如果文件没有就创建
O_TRUNC当对已有内容文件做写入时,会对文件内容清空
O_APPEND每次文件打开向文件结尾处写,追加
宏的传参方式

如果传五个参,要设置五个参数比较低级,可以设置标志位
设置一个参数,参数类型是int, 每个bit位代表一个flag

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

#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)

void Print(int flag)
{
	if(flag & ONE) printf("1\n");
	if(flag & TWO) printf("2\n");
	if(flag & THREE) printf("3\n");
	if(flag & FOUR) printf("4\n");
	if(flag & FIVE) printf("5\n");

	return;
}

int main()
{
	Print(ONE);
	printf("----------------------\n");
	Print(THREE);
	printf("----------------------\n");
	Print(ONE|TWO|THREE);
	printf("----------------------\n");
	Print(THREE|FOUR|FIVE);
	
	return 0;
}

daim
对于open函数中的flag参数也是如上那么使用

open

先学习三个参数的
mode : 权限设置

普通文件:0666

在这里插入图片描述

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


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

	close(fd);
	return 0;
}

在这里插入图片描述
两个参数的open在没有文件的时候也能创建,但是创建文件的权限是乱的
因此需要三个参数的open的第三个参数mode去设置创建文件的权限

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


int main()
{
	int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);

	close(fd);
	return 0;
}

在这里插入图片描述
但是上面创建的文件的属性和我们设置的0666还是不一样的,它是0644。那是因为操作系统有自己的掩码umask

不使用系统的umask,想使用自己的可以在代码中使用umask()函数进行设置
在这里插入图片描述

在这里插入图片描述

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


int main()
{
	umask(0);
	int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);

	close(fd);
	return 0;
}

在这里插入图片描述

如果文件存在,使用两个参数的open,如果文件不存在,使用三个参数的open

关闭文件

利用返回值关闭文件

用那个整数文件描述符
在这里插入图片描述
在这里插入图片描述

返回值
为何默认从3开始?0、1、2已经被用了,stdin – 0、stdout–1、stderror – 2
数组下标?

写文件

write

strlen(str)不要+1 : \0不是字符串的内容,是C语言规定字符串以、0结尾。写入就是乱码

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

int main()
{
	//写文件
	int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);
	if(fd == -1)
	{
		perror("open");
		return 1;
	}
	char* str = "hello world";
	ssize_t i = write(fd, str, strlen(str));
	if(i < 0)
	{
		perror("write");
		return 1;
	}


	close(fd);
	return 0;
}

在这里插入图片描述

系统调用写文件,不会清空文件,直接覆盖式的往里写
在这里插入图片描述
参数flag使用O_TRUNC,即可清空文件里的内容再往里面写
在这里插入图片描述

读文件

在这里插入图片描述
返回值:ssize_t有符号的整数

fd:要读取文件的描述符
buf:读出的内容存放的缓冲区
count:放入内容最大可以存放多少

=0文件结束
<0读取错误
>0读取成功,读到多少个字符
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

int main()
{


	int fd = open("log.txt", O_RDONLY);
	char str[1024];

	ssize_t s = read(fd, str, sizeof(str)-1);
	str[s] = '\0';
	printf("%s\n", str);

	close(fd);

结论

1、C语言的文件接口,本质是封装了系统调用。不仅在接口进行封装,在类型上进行封装
2、FILE是C标准库自己封装的一个结构体,这个结构体里一定包含open返回的那个fd(0.1.2.3…)
3、为何C语言要封装?为了保证自己的跨平台性和可移植性
4、文件描述符的本质:数组下标

引入文件描述符fd、对文件的理解

文件描述符的本质:数组下标

理解一切皆文件

方法集

对于文件的描述struct file 里面有一块是函数指针,函数指针指向具体的硬件的调用方法

文件fd的分配规则

现象1:
关掉0文件,再打开自己文件,看其文件描述符
tu
最小的没有被使用的数组下标,会分配给最新打开的文件
现象2:
关掉1,打印时并没有在屏幕里打印出来,竟然把信息写入到了文件里面
输出重定向:语言层不变,在操作系统层进行更改
在这里插入图片描述

重定向

代码的重定向

输入重定向

输入重定向,从键盘输入的重定向,stdin – 0

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

int main()
{
	close(0);
	int fd = open("log.txt", O_RDONLY);
	char buffer[1024];
	while(1)
	{
		char* s =  fgets(buffer, sizeof(buffer), stdin);
		if(s == NULL) break;
		printf("file content:%s", buffer);
	}
	close(fd);
	return 0;
}

输出重定向

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

int main()
{
	char* str = "hello world";
	int fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);
	dup2(fd, 1);
	fprintf(stdout, "file %s\n", str);
	close(fd);
	return 0;
}

追加重定向

不想利用文件描述符的分配规则,直接打开,打开之后再拷贝到1的位置,也完成了重定向
在这里插入图片描述
在这里插入图片描述
最终只剩old

dup2(fd, 1);
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

int main()
{
	char* str = "hello world";
	int fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);
	dup2(fd, 1);
	fprintf(stdout, "file %s\n", str);
	close(fd);
	return 0;
}

在这里插入图片描述

指令重定向

输入重定向

没有对应的文件会创建
在这里插入图片描述
什么也不做的时候会被清空
在这里插入图片描述

输出重定向

在这里插入图片描述

追加重定向

在这里插入图片描述

修改之前的shell,让它支持重定向

缓冲区

概念

缓冲区是一块内存空间
提高使用者效率
刷新:聚集数据,一次拷贝,提高整体效率
我们一直在说的缓冲区和内核中的缓冲区没有关系,语言层面的缓冲区,C语言自带缓冲区

调用系统调用是有成本的,时间&&空间
单次调用一次系统调用就可以完成工作是效率比较高的

在这里插入图片描述
语言层缓冲区刷新
(1)无刷新,无缓冲
(2)行刷新 ----- 显示器,XXXX\nYY
(3)全刷新 ----- 普通文件,缓冲区写满才刷新

例外
强制刷新
进程退出,要自动刷新
内核中的缓冲区
OS自主决定

在哪里

缓冲区在大写的FILE*内部,像stdin、stdout、stderror都是一个FILE里面都有一个缓冲区
如何证明这个缓冲区的存在呢?
在这里插入图片描述
在这里插入图片描述
子进程先退出,退出时对于缓冲区需要进行刷新,刷新就是修改,修改就要写时拷贝,因此打了两遍

缓冲区还要支持格式化输入输出的实现
键盘显示器的输入和显示都是字符级的,格式化输入输出,就是把字符转换成int、string...,或者int、string...转换成字符级的

磁盘级文件(文件系统)

如何让系统快速定位一个文件?
文件系统,通过路径快速定位文件

例如:菜鸟驿站取快递

磁盘

磁盘的机械构成

盘片、磁头(一面一个磁头)、马达、伺服系统(识别指令并控制盘片和磁头)

磁盘的物理存储

磁道/柱面:一圈
扇区:磁道中的某一部分成为扇区,磁盘IO的基本单位,不一定是系统的

当你想要修改某一个扇区中的一个bit位,必须把整个扇区加载到内存里,修改要修改的bit位,之后再将整个扇区的信息写入。一般512字节

磁头/盘面:一个磁头一个盘面,一个盘面一个编号

访问某一个扇区:
CHS定位法
1、通过磁头定位在哪个磁道/柱面 cylinder
2、使用哪一个磁头/盘面head
3、使用哪一个扇区sector

磁盘的逻辑存储

逻辑存储方便软件的编写
LBA地址logical block address
虽然把内容进行了抽象,但是一次管理这么多还是不容易,所有分成几块,分区
大的分区再分成很多很多组

一个文件一个inode(大小128字节)

删除文件:不会删除内容,只要把inode bitmap和block bitmap使用的置为0就好了
Linux磁盘文件的特性:内容+属性

内容和属性分开存储
内容大小不确定,可能很大,可能很小
属性固定大小的
文件名不属于文件属性

ll -i //显示inode

在这里插入图片描述
系统中,表示一个文件吧不是用文件名,而是inode
每个inode的大小:128字节

inode Bitmap:知道inode Table的使用情况
Data blocks:数据区
Block Bitmap:块位图
Group Descriptor Table:块组描述符表

系统里面创建一个文件的步骤
在某一个块创建一个文件,首先查Inode Bitmap找一个没有被使用的inode,将该文件的相关属性写到找的那个inode在inode table里面。当对此文件进行写入数据的时候,先查Block Bitmap看看数据区的哪一块可以使用,写入到数据区相应的块。之后在该文件的inode里面的有个int block[15]写到这里面,让文件知道自己用了哪个块

inode在整个分区内唯一
在这里插入图片描述

1、首先确定inode在哪个组里

1、红色部分

对于一个文件,int block只有15块吗,这么小?不是的,有直接索引、间接索引、三级索引
在这里插入图片描述

2、蓝色部分

在这里插入图片描述
GDT,Group Descriptor Table:块组描述符,对分组进行管理,描述块组属性信息
超级块(Super Block)存放整个分区情况,对分区做管理,一个文件系统对应一个super block。一个区里选n个group里面有super block,内容是一致的
在这里插入图片描述
每个分区可以写入相同或不同的文件系统 — super block

目录是不是文件?是。inode(inode编号)+ 目录的内容(文件名和inode的映射)
当目录没有w权限,在该目录里面就没法创建文件,没法删除文件
当目录没有r权限,就读不到文件名和inode的映射信息,也就读不到该目录下的文件信息

创建文件
(1)查询inode bitmap查找哪个inode没有用,在相应位置创建inode
(2)将inode编号和文件名的对应写在相应目录的内容里
删除一个文件
(1)从目录里面找到要删文件名对应的inode
(2)将inode bitmap和block bitmap该文件使用的位置设置为0
(3)删除该文件在相应目录内容里的inode和文件名的对应

那么访问目录的内容也是需要inode的,哪里来的

文件的增删查改和路径有关

根目录的inode,开机OS是知道的
查一个文件:在内核中,都要逆向的递归般得到/,从根目录进行路径解析

struct dentry{ }每打开一个路径都会缓存路径,缓存成一个dentry

一个被写入文件系统的分区,要被linux使用,必须要先把这个具有文件系统的分期进行==“挂载”==

挂起:一个文件系统所对应的分区,挂载在对于目录里面
每一个分区都会挂载在一个目录下在这里插入图片描述
访问一个文件,可以根据路径前缀,优先区分文件在哪一个分区下

具体的文件系统

软硬链接

原理

软连接
在这里插入图片描述

软链接本质:是一个文件,因为它有自己的inode
软件内容放的是目标文件的路径,类似快捷方式

硬链接
在这里插入图片描述

硬链接:本质不是一个独立的文件,因为它的inode编号和目标文件的一样。一定没有新建文件
是新的文件名,和目标文件inode号的映射关系,写入到指定的目录的数据库中

硬链接像重命名,引用计数
在这里插入图片描述
在这里插入图片描述

应用场景

删除一个文件

rm -f 文件名
unlink 文件名

1、软链接
当我们写了一个项目mytest要传给其他人,不想把原始代码传给他,只传bin、conf、log、myexe
在这里插入图片描述
对方要运行myexe就需要./bin/myxe这样很麻烦,因此可以用一个软连接
在这里插入图片描述
2、硬链接
当创建一个空文件的时候,它的硬链接数就是2,为什么?
在这里插入图片描述
因为他有一个.
在这里插入图片描述

OS不允许用户自己给目录简历硬链接,因为会形成环路问题
OS可以自己建立,eg. ..

动静态库

回顾

1、默认安装的是动态库,云服务器,静态库(c标准库)默认没有安装
2、默认编译程序,用的是动态链接的

动态库的制作和使用

为什么要有库?
提高开发效率
隐藏源代码

1、建立静态库

当没有源代码的时候,只有.o .h就可以使用里面的方法
假设我有一些写好的方法,user这个用户想要使用,但我不想给他我的源代码,就可以只给他.o .h文件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

但是当.o文件很多的时候很麻烦,而且难免会有遗漏的

ar -rc libmyc.a  打包文件 打包文件

在这里插入图片描述
库名:myc,去掉前缀、去掉后缀
在这里插入图片描述

-l:链接哪个库
-L:连接的库在什么地方,因为系统只默认知道lib64下的,所以这要指明

在这里插入图片描述
2、形成动态库并发布
转换成.o文件
fPIC :产生位置无关码

//形成.o文件
gcc -c -fPIC 文件名

动态库打包
shared:表示生产共享库格式

gcc -shared -o libxxx.so xxxx.o xxxx.o

库的名字以so结尾
在这里插入图片描述
但此时给使用者动态库,还是需要给动态库、.h等文件,我们可以把它们合并成一个

libmyc.so: mymath1.o mymath2.o
	gcc -shared -o $@ $^

%.o:%.c
	gcc -c -fPIC $<
#mymath1.o:mymath1.c
#	gcc -c -fPIC $<
#mymath2.o:mymath2.c
#	gcc -c -fPIC $<

.PHONY:clean
clean:
	rm -f *.o mylib libmyc.so

.PHONY:output
output:
	mkdir -p mylib/include
	mkdir -p mylib/lib
	cp -rf *.h mylib/include
	cp -rf *.so mylib/lib

其中$<代表依赖的对象从左到右一个一个执行

在这里插入图片描述
还可以吧mylib压缩一下
在这里插入图片描述
在这里插入图片描述

不想使用-L:把这个库拷贝到/lib64

//看可执行程序连接了那些库                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
ldd xxx.out

4、用户下载
在这里插入图片描述

下载使用很麻烦,把.h和库拷贝到默认路径下,就可以不用带了
在这里插入图片描述

所谓的把库(其他软件)安装到系统中,本质就是把文件拷贝到指定路径

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

5、卸载
不建议把自己写的不成熟的库写到默认路径里
在这里插入图片描述

注意
静态库只需要编译的时候使用,之后运行什么的就不需要了(只影响编译阶段)
动态库:(1)编译时搜索路径 ---- gcc(2)运行时的搜索路径 — 操作系统
对于动态库在编译的时候可以跑通,运行的时候又找不到库了
在这里插入图片描述

解决方法
1、把自己的库拷贝到默认路径下/lib64,既可以支持编译、又可以支持运行
2、将不在系统默认库搜索路径下的库连接,添加到LD_LIBERERY_PATH
在这里插入图片描述:系统运行程序时,动态库查找时的辅助,:为分隔符
export LD_LIBERERY_PATH=$LD_LIBRARY_PATH:具体路径
问题:重新登陆就没有了,但是在家目录里面的.bashrc里配置
3、对库文件在默认库路径下建立同名软连接
4、配置文件:把路径放到配置文件里就可以
/etc/ld.so.conf.d在该目录下创建配置文件
ldconfig使配置文件生效

理解动态库加载

1、同时形成动静态库,默认动态链接
2、非要静态链接必须使用选项-static
3、如果只提供静态库,那我们的可执行程序也没办法,只能对该库进行静态链接,但是程序不一定整体是静态链接
4、如果只提供动态库,默认只能动态链接,非得静态链接,会发生连接报错

系统角度理解

库函数的调用,依旧在进程的地址空间中进行的
动态库加载之后,会映射到进程的共享区

1、谁来决定,哪些库加载了,那些没加载?OS会自动决定
2、系统中可以同时有多个库吗?可以
3、操作系统对这些库先描述再组织

编址

可执行程序加载之前,就有地址
我们进程地址空间里面很多地址数据,是从可执行程序里面来的(因为不同的可执行程序,他的数据段和代码段是不一样的,进程初始化进程地址空间每块多大的时候就是根据可执行程序)
可执行程序编制方式:
绝对编址方式—平坦模式(从0开始编址)
相对地址—逻辑编制(偏移量)
==虚拟地址空间本身不仅OS要遵守,编译器编译程序的时候,也要遵守

理解动态库动态链接和加载问题

1、进程的地址空间既然是一个数据结构对象,谁来用什么数据初始化呢?
可执行程序本身在加载到内存之前,根据自身表头的数据部分初始化进程地址空间的数据段、代码段等的begin end

;