缓冲区
缓冲区的意义在于节省进程进行数据IO的时间,本质是一段内存。在用户级语言层面提供的,在stdout、stdin、stderr,是一个FILE*类型(FILE是一个结构体)
如果对进程实现输出重定向并调用printf fwrite库函数和write系统调用就会发现printf 和 fwrite都输出了2次,而 write 只输出了一次。因为fork()后会生成子进程,一般C库函数写入文件时是全缓冲的而写入显示器是行缓冲。printf fwrite 库函数会自带缓冲区,发生重定向到普通文件时数据的缓冲方式由行缓冲变成了全缓冲。而放在缓冲区中的数据就不会被立即刷新,甚至fork之后直至进程退出之后才会统一刷新写入文件当中。但是fork的时候父子数据会发生写时拷贝,所以当父进程准备刷新的时候子进程也就有了同样的一份数据,随即产生两份数据。write没有变化,说明没有所谓的缓冲
缓冲区的刷新策略:
- 立即刷新–fflush()–无缓冲
- 行刷新–显示器(输出带上\n就会行刷新,显示器为了满足人的需求从左到右一行读的习惯就会采用行刷新)
- 缓冲区满(全缓冲)–磁盘文件–效率最高只需要一次IO
- 进程退出时
重定向会导致刷新策略发生了改变(由行缓冲变成了全缓冲)同时发生了写时拷贝父子进程各自刷新。因此如果没有重定向时调用printf+\n就会直接打印到显示器中,如果重定向了没有强制去刷新那么printf到普通文件中的数据并不会被刷新。
动静态库
静态库:
程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库 .a
动态库:
程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码 .so后缀
只需要有目标文件和头文件 那么程序就可以链接成可执行程序
如果要链接第三方的库,可以将这个库安装到系统默认指定的路径中(为了防止污染系统尽量不使用这种方式)或者指明库所在的路径和库的名称(去掉前缀和后缀)同时也要指明头文件所在的路径。需要注意即使是将第三方库安装到系统中,也需要指明库的名称才能编译,因为gcc只是知道了库的位置但gcc只认C语言的库所以需要用户自己指明。gcc默认动态链接,对于特定的库取决于用户提供的是动态库还是静态库
需要三个选项完成链接:
-I(大写i):指明头文件的搜索路径
-L: 指明库文件的搜索路径
-l(小写L):指明要链接哪个库,带上库的名称(去掉前缀和后缀)
生成静态库
将所有的目标文件打包起来就叫做静态库
首先需要用到指令:
*ar -rc XXX.a .o
将所有的目标文件打包到一个目录(库)中,再将所有的头文件放到一个目录中,最后合并这两个目录就形成了一个软件包
生成动态库
- 生成目标文件时需要加上 -fPIC (与位置无关码)选项
- 打包目标文件成动态库时需要加上 - shared 选项。因为gcc默认为动态链接所以gcc就可以完成动态库打包 gcc -shared -o XXX.so *.o
- 最后再将所有的头文件和库合并成一个软件包
运行动态库
因为gcc只负责编译程序而动态库是在运行的时候才链接的,所以执行程序的话操作系统需要知道动态库在哪里。
可以采取的方法:
动态库的环境变量:
$LD_LIBRARY_PATH
所以第一种方法可以把动态库的路径添加到这个环境变量里,但是这种方法不是永久有效的,环境变量只会在当前次登录系统才有效
第二种方法将软件包安装到系统的默认路径
第三种方法:
在系统里有一个路径里面全都是动态库配置文件,只需要将动态库的路径写到这个路径里即可
/etc/ld.so.conf.d/
写好配置文件后需要更新一下 ldconfig
操作完之后这个方式是永久有效的
第四种方法:
再当前目录对库所在的路径创建软链接,这样操作系统运行时就会在当前目录找到库的路径就可以访问的到了
动静态库的加载
静态库不需要加载,将代码拷贝到代码区,通过确定的地址进行访问
动态库需要加载。动态库中的指定函数的地址会写到可执行程序中,而这个地址因为在编译时加上了shared选项形成了位置无关码,所以采用的是相对地址的方法。
在程序运行时,添加到可执行程序中的是一个偏移量,库加载进来后用过偏移量就可以找到需要访问的函数
系统层面上会维护动态库的起始地址,直接建立页表与内存的映射就可以实现跳转访问,所以动态库加载一次就可以被多个进程共同使用了