我们使用ls -l的时候看到的除了看到文件名,还看到了文件元数据
[root@localhost linux]# ls -l
总用量 12
-rwxr-xr-x. 1 root root 7438 "9月 13 14:56" a.out
-rw-r--r--. 1 root root 654 "9月 13 14:56" test.c
每行包含7列:
- 权限
- 硬链接数
- 文件所有者
- 组
- 大小
- 最后修改时间
- 文件名
ls -l读取存储在磁盘上的文件信息,然后显示出来
其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息
[root@localhost linux]# stat test.c
File: "test.c"
Size: 654 Blocks: 8 IO Block: 4096 普通文件
Device: 802h/2050d Inode: 263715 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-09-13 14:56:57.059012947 +0800
Modify: 2017-09-13 14:56:40.067012944 +0800
Change: 2017-09-13 14:56:40.069012948 +0800
上面的执行结果有几个信息需要解释清楚
为了能解释清楚inode我们先简单了解一下文件系统
Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被
划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设
定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的,
- Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。就像我们一个省,地域太广了,要划分为市进行管理
- 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:block 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了,每个Block group都包含一个Super Block,是为了备份
- GDT,Group Descriptor Table:块组描述符,描述当前块组属性信息,比如哪些inode使用了,哪些没使用,哪些Data blocks使用了,哪些没使用等。
- inode Table节点表:inode存放文件属性 如 文件大小,所有者,最近修改时间等
- Data Blocks数据区:存放文件内容
- 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
- inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。
inode:存放文件的属性
Data blocks:存放文件的数据
文件个数:inode个数:Datablock = 1:1:n
inode必须包含datablocks对应的映射关系 unsigned map[n],也就是一个文件使用的datablock有哪些。
如何找到每个inode呢?inode实际上是有id的,要找到文件的数据,就要先找到文件的inode,因为inode中有数据块的银映射,而要找到inode,就要找到inode的id。
所以inode Bitmap实际上就是管理着inode的id,哪些id被使用了,哪些没有被使用,负责分配inode编号。
将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过touch一个新文件来看看如何工作。
[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
263466 abc
为了说明问题,我们将上图简化(缺少了一些部分) :
创建一个新文件主要有一下4个操作:
存储属性
内核先找到一个空闲的inode id(这里是263466)。内核把文件属性信息记录到其中。存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。记录分配情况
文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。也就是在inode中记录所占用的data block。还有inode Bitmap会将该id标为已使用,Block Bitmap中会将300、500、800标记为已使用,以及GDT中也就对当前Block Group的inode id和Data block使用情况进行更新
4. 添加文件名到目录
新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和 inode 之间的对应关系将文件名和文件的内容及属性连接起来。
目录也是文件 = inode + 数据块
数据块中保存的是该目录下的文件名与inode id 的对应关系。
我们看到,真正找到磁盘上文件的并不是文件名,而是inode。 其实在linux中可以让多个文件名对应于同一个inode。
使用命令:ln log.txt link
792179 drwxrwxr-x 2 ysj ysj 4096 Oct 5 00:37 file //file是一个目录
792176 -rw-r--r-- 2 ysj ysj 24 Oct 4 23:32 link
792176 -rw-r--r-- 2 ysj ysj 24 Oct 4 23:32 log.txt
792156 -rw-rw-r-- 1 ysj ysj 0 Oct 5 00:25 myfile
第三列的2表示硬链接数
- link和 log.txt的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode
792176 的硬链接数为2。- link和log.txt不是独立的文件
- file没有使用
ln
命令,为什么也有两个硬链接数呢?因为在file目录下,默认还有一个隐藏的.文件,这个.文件与file共同形成了两个硬链接。同理…文件也是一个硬链接- 我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则将对应
的磁盘释放。
硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件
使用命令示例:ln -s myfile flink
将flink成为myfile的软链接,flink和myfile是独立的文件
792178 lrwxrwxrwx 1 ysj ysj 6 Oct 5 00:29 flink -> myfile
792176 -rw-r--r-- 2 ysj ysj 24 Oct 4 23:32 link
792176 -rw-r--r-- 1 ysj ysj 24 Oct 4 23:32 log.txt
792156 -rw-rw-r-- 1 ysj ysj 0 Oct 5 00:25 myfile
软链接的应用:如果我们要执行的可执行文件不在当前目录,就可以利用软链接,在当前目录下生成一个可执行文件,从而不必使用路径来调用可执行文件
注意:软硬连接就好像文件的引用(C++中的引用),任意一份链接修改了,其它的链接都会同步修改,但软链接区别于硬链接,软链接有自己的innode id,是一个独立的文件
下面解释一下文件的三个时间:
Access 最后访问时间
Modify 文件内容最后修改时间
Change 属性最后修改时间
通过命令ldd + 可执行文件名,可以查看可执行文件中调用的库
如:libc.so -> c库,去掉前缀lib,去掉后缀.so或.a
使用动态链接生成的可执行程序体积小,依赖库,在程序运行的时候加载库函数,可以只有一份
使用静态链接生成的可执行程序体积较大,它是将对应的代码拷贝进可执行程序,该可执行程序的可移植性强。
动态链接
静态链接
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静
态库- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文
件的整个机器码- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个
过程称为动态链接(dynamic linking)- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚
拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间
将我们自己实现的add、sub打包成库使用
add和sub的内容:
//测试程序
/myadd.h/
#ifndef __MYADD_H__
#define __MYADD_H__
int add(int a, int b);
#endif // __MYADD_H__
/myadd.c/
#include "myadd.h"
int add(int a, int b)
{
return a + b;
}
/mysub.h/
#ifndef __MYSUB_H__
#define __MYSUB_H__
int sub(int a, int b);
#endif // __MYSUB_H__
/sub.c/
#include "mysub.h"
int sub(int a, int b)
{
return a - b;
}
///main.c
#include
#include "myadd.h"
#include "mysub.h"
int main( void )
{
int a = 10;
int b = 20;
printf("add(10, 20)=%d\n", a, b, add(a, b));
a = 100;
b = 20;
printf("sub(%d,%d)=%d\n", a, b, sub(a, b));
}
将add.c、sub.c生成add.o和sub.o,然后进行打包生成静态库
将头文件和生成的静态库放在mylib下,而mylib中又有include和lib目录,分别将头文件放入include目录中、静态库放入lib中
生成静态库
ar -rc的语法格式:ar -rc 生成的静态库名称 想要打包的.o文件
[ysj@VM-4-11-centos study_10-5]$ gcc -c mysub.c -o mysub.o
[ysj@VM-4-11-centos study_10-5]$ gcc -c myadd.c -o myadd.o
[ysj@VM-4-11-centos study_10-5]$ ls
makefile myadd.c myadd.h myadd.o mysub.c mysub.h mysub.o test.c
//生成静态库
[ysj@VM-4-11-centos study_10-5]$ ar -rc mylibmath.a *.o
[ysj@VM-4-11-centos study_10-5]$ ls
makefile myadd.c myadd.h myadd.o mylibmath.a mysub.c mysub.h mysub.o test.c
ar是gnu归档工具,rc表示(replace and create)
也可以使用makefile
libmymath.a:myadd.o mysub.o
ar -rc $@ $^
myadd.o:myadd.c
gcc -c $<
mysub.o:mysub.c
gcc -c $<
.PHONY:clean
clean:
rm -rf *.o output libmymath.a
.PHONY:output
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp *.h ./mylib/include
cp *.a ./mylib/lib
# make output:发布,生成对应目录,并将头文件和库拷贝至指定目录
查看静态库中的目录列表
[ysj@VM-4-11-centos study_10-5]$ ar -tv mylibmath.a
rw-rw-r-- 1001/1001 1240 Oct 5 14:01 2021 myadd.o
rw-rw-r-- 1001/1001 1240 Oct 5 14:01 2021 mysub.o
t:列出静态库中的文件
v:verbose 详细信息
将静态库与头文件放入对应的目录下:
makefile中
test:test.c
gcc test.c -o test -I./mylib/include -L./mylib/lib -lmymath -static
.PHONY:clean
clean:
rm -f test
# -I,因为我们的头文件不在系统的默认目录或路径下,所以需要向编译器说明我们的头文件在哪。-I选项 + 头文件路径就表示告知编译器我们的头文件的路径
# -L,同样我们的库也不在默认目录与路径下,也需要向编译器说明我们生成的静态库在哪。-L选项 + 库的位置
# -l,虽然告诉了编译器头文件和目录的位置,但实际上,编译器不知道我们要用哪个库啊,一个lib目录下可能有多个库,所以要说明使用的库的名字(去掉后缀和前缀)
# -static 表示采用静态链接的方式编译,使用前需下载相关文件。使用命令:sudo yum install glibc-static
test.c内容:
#include"myadd.h"
#include"mysub.h"
#include
int main()
{
int a = 10;
int b = 20;
printf("add(10, 20)=%d\n", a, b, add(a, b));
a = 100;
b = 20;
printf("sub(%d,%d)=%d\n", a, b, sub(a, b));
return 0;
}
- 从左到右搜索-L指定的目录。
- 由环境变量指定的目录 (LIBRARY_PATH)
- 由系统指定的目录
- /usr/lib
- /usr/local/lib
所以我们还可以将头文件和库放在指定目录下进行编译,不过不建议这样做。
使用makefile生成
libmymath.so:myadd.o mysub.o
gcc $^ -shared -o $@
myadd.o:myadd.c
gcc -fPIC -c $<
mysub.o:mysub.c
gcc -fPIC -c $<
.PHONY:clean
clean:
rm -rf *.o output libmymath.so
.PHONY:output
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp *.h ./mylib/include
cp *.so ./mylib/lib
- shared: 表示生成共享库格式
- fPIC:产生位置无关码(position independent code)
- 库名规则:libxxx.so
效果:
命令行:
[root@localhost linux]# gcc -fPIC -c mysub.c myadd.c #生成.o文件
[root@localhost linux]# gcc -shared -o libmymath.so *.o # 生成动态库
[root@localhost linux]# ls myadd.c myadd.h myadd.o libmymath.so test.c mysub.c mysub.h mysub.o # 展示生成结果
编译选项
与静态库相同,少了-static选项
gcc test.c -o test -I./mylib/include -L./mylib/lib -lmymath
为什么不行呢?
查看可执行文件test所需要的库:
发现我们自己打包的静态库并没有被找到,因为我们是按动态链接来编译的,编译器知道了我们的库在哪,可是操作系统知道嘛?
所以,我们要引入一个环境变量 LD_LIBRARY_PATH
,里面放着动态库的路径,我们要把自己的库的路径也放进去。
于是,要执行命令 export LD_LIBRARY_PATH=./mylib/lib/
此时,就可以正常执行了:
将动态库的使用写在makefile里:
test:test.c
gcc test.c -o test -I./mylib/include -L./mylib/lib -lmymath #-static
.PHONY:clean
clean:
rm -f test
1、拷贝.so文件到系统共享库路径下, 一般指/usr/lib
2、更改LD_LIBRARY_PATH
[root@localhost linux]# export LD_LIBRARY_PATH=. [root@localhost linux]# gcc main.c -lmymath [root@localhost linux]# ./a.out add(10, 20)=30 sub(100, 20)=80
3、ldconfig 配置/etc/ld.so.conf.d/,ldconfig更新
[root@localhost linux]# cat /etc/ld.so.conf.d/bit.conf /root/tools/linux [root@localhost linux]# ldconfig