Linux | 文件系统 基础IO

目录

C语言文件的IO操作

文件的打开与关闭

文件打开模式:

文件操作实例

常见的文件操作函数

系统文件操作接口

open()函数

close()函数

read()函数

write()函数

使用以上接口

写入操作

读取操作

系统调用与库函数

文件描述符

文件描述符的分配规则

重定向

重定向缓冲区的理解

文件系统

磁盘存储

Block Group

inode

文件在Block Group中的存储

位图

文件的创建、删除、恢复

硬链接和软链接

硬链接

软链接

stat

动态库和静态库

动态库和静态库的基础知识

动态库和静态库的制作

总结


C语言文件的IO操作

回顾一下我们之前学习过的C语言文件IO操作的函数接口:

文件的打开与关闭

我们通过C程序打开文件时,会返回一个FILE*的指针变量指向该文件,相当于建立了指针和文件的关系。

ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

fopen和fclose函数原型:

//打开文件函数:
FILE *fopen( const char *filename, const char *mode );
//参数:
    filename为打开文件的名称
    mode为打开文件的模式
//返回值:
    打开成功,返回一个指向该文件的指针,失败返回NULL
//关闭文件函数:
int fclose( FILE *stream );
//参数:
    stream就是打开文件时用来接收指向文件结构体的地址的指针
//返回值
    成功关闭返回0,失败返回EOF

文件打开模式:
 

模式

操作区别要求
r从文件头开始文件需存在
r+读写从文件头开始文件需存在
w从文件头开始文件不存在则创建,存在则清空
w+读写从文件头开始文件不存在则创建,存在则清空
a从文件尾开始文件不存在则创建,存在则追加
a+读写从文件头读取,从文件尾写入文件不存在则创建,存在则追加

文件操作实例

#include <stdio.h>
#include <stdlib.h>

int main()
{
	//打开文件:
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen:");
		return -1;
	}
	
	//业务操作

	//关闭文件:
	fclose(pf);
	pf = NULL;
	return 0;
}

常见的文件操作函数

功能函数名适用于
字符输入函数fgetc

所有输入流

字符输出函数fputs所以输出流
文本行输入函数fgets所以输出流
文本行输出函数fputs所以输出流
格式化输入函数fscanf所以输出流
格式化输出函数fprintf所以输出流
二进制输入fread

文件

二进制输出fwrite文件

 更多的文件操作函数详见:<cstdio> (stdio.h) - C++ Reference (cplusplus.com)

由于本文是对Linux下文件基础IO的探究,所以对C语言文件操作不再做过多阐述,对C语言文件操作还不太熟悉的同学可以自行查阅学习,下面进入到我们的主题,Linux下的文件操作。

系统文件操作接口

Linux下常用的文件操作接口有:

  • open()   //打开文件
  • close()   //关闭文件
  • read()    //读取文件
  • write()   //写入文件

open()函数

open()函数用于打开一个文件,并返回一个文件描述符。语法如下:

int open(const char *pathname, int flags, mode_t mode);
//参数说明:
    pathname:要打开的文件路径。
    
    flags:打开文件的标志,可以是以下之一或多个的组合:
        O_RDONLY:只读模式。
        O_WRONLY:只写模式。
        O_RDWR:读写模式。
        O_CREAT:如果文件不存在,则创建文件。
        O_APPEND:在文件末尾追加数据。
        O_TRUNC:截断文件,即清空文件内容。
    
    mode:创建文件时的权限。

//返回值:
    成功返回新的文件描述符fd,失败返回-1
    

close()函数

close()函数用于关闭一个已打开的文件。语法如下:

int close(int fd);
//参数说明:
    fd:文件描述符。
//返回值
    成功返回0,失败返回-1

read()函数

read()函数用于从文件中读取数据。语法如下:

ssize_t read(int fd, void *buf, size_t count);
//参数说明:
    fd:文件描述符。
    buf:保存读取数据的缓冲区。
    count:要读取的字节数。
//返回值:
    成功时返回读取的字节数,失败返回-1

write()函数

write()函数用于向文件中写入数据。语法如下:

ssize_t write(int fd, const void *buf, size_t count);
//参数说明:
    fd:文件描述符。
    buf:要写入的数据。
    count:要写入的字节数。
//返回值:
    成功时返回写入的字节数,失败返回-1

使用以上接口

我们来简单学习一下上面的文件操作接口

写入操作

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
int main()
{
    int fd=open("test.txt",O_WRONLY|O_CREAT,0644);//0644为创建出来的文件的权限
  
    //写入操作
    const char *s="hollow document";

    write(fd,s,strlen(s));//将s数据中的前strlen(s)个字节写入到fd打开的文件中

    close(fd);

    return 0;
}

运行结果

[user@localhost linux]# ./test
[user@localhost linux]# ll
total 20
-rwxrwxr-x 1 user user 8512 Sep  3 09:06 test
-rw-rw-r-- 1 user user  268 Sep  3 09:06 test.c
-rw-r--r-- 1 user user    9 Sep  3 09:06 test.txt
[user@localhost linux]# cat test.txt
hello document

其中我们在使用open函数创捷文件时,文件的权限一定要带上,否则会出现下面这种情况

int main()
{
    int fd=open("test.txt",O_WRONLY|O_CREAT,/* 0644 */); //没有设置文件权限
  
    //写入操作
    const char *s="hollow document";

    write(fd,s,strlen(s));

    close(fd);

    return 0;
}

运行结果

[user@localhost linux]# ./test
[user@localhost linux]# ll
total 20
-rwxrwxr-x 1 user user 8512 Sep  3 09:06 test
-rw-rw-r-- 1 user user  268 Sep  3 09:06 test.c
-----wx--T 1 user user    9 Sep  3 09:06 test.txt
[user@localhost linux]# cat test.txt
cat: test.txt: Permission denied  //没有权限许可

我们发现如果在创建文件时不带上文件权限时,创建出来的文件权限往往不符合我们预期的标准,甚至可能无法进行读写。

读取操作

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

int main()
{
    int fd=open("test.txt",O_RDONLY);//由于文件已经创建好了,我们只需要以只读的方式打开
      
    // 读取操作 
    const char* s="hollow document";

    char ss[16];

    read(fd,ss,strlen(s));//从fd打开的文件中读取strlen(s)长度的字节到ss中

    printf("%s\n",ss);

    close(fd);

    return 0;
}

运行结果

[user@localhost linux]# ./test
hello document

系统调用与库函数

  • 我们之前在C语言中学习过的的 fopen fclose fread fwrite 都是C语言库中为我们提供的函数,我们称为库函数(libc)。
  • open close write read 都属于操作系统为我们提供的接口,我们称为系统调用

关于系统调用与库函数之间的关联,我们可以看下面这张图:

由上图我们可以看出,所有语言库的函数都是对系统调用的二次封装,已便于编程人员进行二次开发。

文件描述符

在上文讲解系统文件操作接口时有一个出现了很多次很重要的概念——文件描述符

我们知道一个进程可以打开多个文件,操作系统在创建进程时会为每一个进程维护独特的PCB,PCB的具体结构如下图:

  1. 进程标识符(PID):用于唯一标识一个进程的整数值。
  2. 进程状态(state):表示进程的当前状态,如运行、等待、停止等。
  3. 进程优先级(prio):用于确定进程在调度时的优先级。
  4. 进程调度策略(policy):指定进程的调度策略,如实时调度或普通调度。
  5. 进程的父进程(parent):指向父进程的task_struct结构体。
  6. 进程的子进程链表(children):指向子进程的task_struct结构体链表。
  7. 进程的兄弟进程链表(sibling):指向同一父进程的其他子进程的task_struct结构体链表。
  8. 进程的文件描述符表(files):包含进程打开的文件描述符及其相关信息。
  9. 进程的运行时间统计(utime、stime):记录进程运行的用户态时间和内核态时间。
  10. 进程的地址空间(mm):用于管理进程的虚拟内存空间。
  11. 进程的信号处理器(signal):用于处理进程接收到的信号。
  12. 进程的调度信息(sched_info):记录进程的调度相关信息,如上次调度时间、调度次数等。

其中有一个文件描述符表(files),files本质上是一个结构体指针,指向了files_struct结构体:

  1. 引用计数(count):表示当前files_struct结构体的引用计数,用于管理结构体的生命周期。
  2. 文件描述符数组(fd_array):是一个指向file结构体指针的数组,用于存储打开的文件描述符和相关信息。
  3. 文件描述符的最大限制(max_fds):表示文件描述符数组的最大长度,即最多可以打开的文件描述符数量。
  4. 文件描述符位图(open_fds):是一个位图,用于快速检查文件描述符是否已经被使用。
  5. 文件描述符表锁(file_lock):用于保护文件描述符表的并发访问。

而在file_struct结构体中,有一个文件描述符数组(fd_array),在这个数组里面存储的是指向file的指针,也就是所谓的文件描述符fd(本质上是一个整数),当我们的进程打开一个文件,执行的操作如下图:

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

现在我们知道,文件描述符其实是文件指针数组的下标,当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体,表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,数组中每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标(这也是为什么文件描述符本质上是一个整数)。所以,只要拿着文件描述符,就可以找到对应的文件。

文件描述符的分配规则

 默认打开的三个文件它们的文件描述符是0,1,2。文件描述符的分配规则是在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

我们可以通过代码来验证一下:

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

int main()
{
    printf("stdin -> %d\n", stdin->_fileno);
    printf("stdout -> %d\n", stdout->_fileno);
    printf("stderr -> %d\n", stderr->_fileno);

    int fd = open("text.txt", O_WRONLY|O_CREAT,0644);
    printf("text.txt -> %d\n",fd);

    close(fd);

    return 0;
}

运行结果

[user@localhost linux_fd]$ ./text 
stdin -> 0
stdout -> 1
stderr -> 2
text.txt -> 3

如果我们把对应的标准输入流(fd:0)关闭,那么新创建的文件是否会使用 0 来作为文件描述符呢?我们继续验证:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
    close(0);
    int fd=open("test.txt",O_WRONLY|O_CREAT,0644);
  
    printf("test.txt -> %d\n",fd);
    close(fd);
    return 0;
}

运行结果

[user@localhost linux_fd]$ ./text 
text.txt -> 0

运行结果符合预期,说明文件描述符是从未使用的最小下标来分配的

重定向

默认情况下,fd=1的是标准输出流,如果我们将fd=1的标准输出流给关闭了,那么我们新创建的文件的fd就是1,系统认为fd=1的就是标准输出,那么原来向屏幕输出的内容,会输出到文件中嘛?我们还是通过代码来验证:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
    close(1);
     int fd=open("test.txt",O_WRONLY|O_CREAT,0644);
  
    printf("test.txt -> %d\n",fd);
    printf("hello linux...\n");
    printf("hello linux...\n");
    printf("hello linux...\n");
    printf("hello linux...\n");
    printf("hello linux...\n");

    fflush(stdout);
    close(fd);
    return 0;
}

运行上面的程序我们发现确实不会输出到屏幕,我们查看创建的文件test.txt:

[user@localhost linux_fd]$ cat test.txt
test.txt -> 1
hello linux...
hello linux...
hello linux...
hello linux...
hello linux...

发现原来向屏幕输出的内容,输出到了创建的文件中,并且此文件的fd=1,其实上面的操作就是一个简单的重定向。

重定向是一种用于改变进程输入和输出的技术。通过重定向,可以将一个进程的标准输入、标准输出或标准错误输出重定向到其他文件或设备上,而不是默认的终端(键盘和屏幕)。

上面的重定向程序中我们用了fflush(),如果不用fflush()可以完成重定向嘛?刷新缓存区不是有\n就可以了,还有必要加上fflush()吗?我们来试一下:

[user@localhost linux_fd]$ cat text.txt
[user@localhost linux_fd]$

我们惊奇的发现原本输入到文件中的内容居然不见了,重定向居然失败了,这是因为虽然在屏幕上打印刷新缓存区可以是换行,但是重定向到文件中,刷新缓存区不是换行,所以内容还在缓冲区中并没有刷新到文件中。可是进程退出,不是也会刷新缓冲区嘛?对的,但是在进程退出前,你已经关闭了文件了。

重定向缓冲区的理解

我们可以通过下面这段代码来加深对重定向和缓冲区的理解:

#include <stdio.h>
#include <string.h>
#include<unistd.h>
int main()
{
 const char *msg0="printf...\n";
 const char *msg1="fwrite...\n";
 const char *msg2="write...\n";

 printf("%s", msg0);

 fwrite(msg1, strlen(msg1), 1, stdout);

 write(1, msg2, strlen(msg2));

 fork();
 
 }

输出到屏幕上面的结果如下:

[user@localhost linux_fd]$ ./test
printf...
fwrite...
write...

如果我们进行重定向:

[user@localhost linux_fd]$ ./test > test.txt
[user@localhost linux_fd]$ cat test.txt
write...
printf...
fwrite...
printf...
fwrite...

这个结果出乎我们的意料,为了弄清楚产生的原因,我们先要了解一下刷新区的机制:

缓存区的刷新机制:

  • 立即刷新:用fflush(),进程退出
  • 行刷新:比如显示器打印
  • 全缓冲:等缓存区满了才会刷新,往磁盘写入

用户端的缓冲区以及操作系统的缓存区

  • 用户端的缓冲区一般是C语言接口函数用的,比如:printf,fprintf等。
  • 操作系统的内核缓冲区是系统调用接口所以用的,比如:write等。

有了以上基础,我们来讲讲为什么会发生上面的情况:

没有发生重定向时,刷新缓冲区的机制是行刷新:所以fork()不会发生写时拷贝,因为fork()之前的内容都直接刷新到屏幕了。发生重定向后,刷新缓冲区的机制变成全缓存,fork()会发生写时拷贝,拷贝缓冲区的内容,所以导致重定向输入到文件中的内容有两份。但是为啥write()不受影响呢?因为它是系统调用接口,它用的缓存区是操作系统的,所以上面的刷新缓存区啥的,和它没关系。我们可以用下面的流程图来更好的理解:

重定向之前

重定向之后

文件系统

在我们的磁盘中存在大量的文件,这些文件是如何存储的又是如何进行管理和组织的呢?

磁盘存储

磁盘存储的最基本原理是电生磁。

磁盘内部有许许多多的盘片,每一个盘片的内部又有许多的磁道,磁道里边有很多的磁颗粒。每个磁颗粒都存储着一个数据,我们通过传动臂将磁头移动到正确的扇面上,通过磁头我们就能够找到存储的数据。

如果存储一串数字:1 2 3 4 5。我们会得到以下比特流:0 1 10 11 100

但是这样做我们在读取时会出现歧义性,因此我们需要按固定字符(8个比特)进行分割
那么我们储存以上的数据就会变成
00000000 00000001 00000010 000000110 0000100

最终会得到一串数字1 2 3 4 5

我们通常把8比特称为1字节单位(B),也是磁盘当中的最小存储单元。

不难发现,我们的磁盘由一个一个的扇区组成,管理好了扇区自然就管理好了磁盘

对扇区的管理还可以下分,将扇区分成许多的小块,从而进行管理,这个小块就是block group

Block Group

Block group(块组)是文件系统中的一个概念,它是将数据块、inode表和相关的元数据组织在一起的一个单元。文件系统将整个存储空间划分为多个块组,每个块组包含一定数量的数据块和对应的inode表。

每个块组通常包含以下几个主要组件:

  1. Super Block(超级块):存储文件系统的整体信息,如文件系统的大小、块大小、inode的数量等。它是文件系统的核心组成部分,用于恢复文件系统的一致性和完整性。

  2. Group Descriptor Table(组描述符表):存储文件系统中每个数据块组的信息,包括数据块组的起始地址、数据块的数量、空闲数据块的数量等。

  3. Block Bitmap(块位图):用于记录文件系统中每个数据块的使用情况,即哪些数据块是已经分配给文件或目录,哪些是空闲的可用于分配的。

  4. Inode Bitmap(inode位图):用于记录文件系统中每个inode的使用情况,即哪些inode是已经分配给文件或目录,哪些是空闲的可用于分配的。

  5. Inode Table(inode表):存储文件系统中所有文件和目录的元数据信息,如文件大小、权限、所有者等。

  6. Data Blocks(数据块):存储文件系统中实际的文件数据。

inode

inode(索引节点)是文件系统中用于存储文件和目录的元数据的数据结构。每个文件和目录在文件系统中都有一个对应的inode,它记录了文件或目录的属性、权限、大小、时间戳等信息。

inode通常包含下面几个字段:

  • 文件类型(File Type):记录文件的类型,如普通文件、目录、符号链接等。

  • 文件权限(File Permissions):记录文件的访问权限,包括文件所有者的权限、所属组的权限和其他用户的权限。

  • 文件大小(File Size):记录文件的大小,以字节为单位。

  • 文件链接数(Link Count):记录指向该inode的硬链接数量。硬链接是指多个文件名指向同一个inode的情况。

  • 所有者(Owner):记录文件的所有者,通常是用户的标识符(如用户ID)。

  • 所属组(Group):记录文件所属的组,通常是组的标识符(如组ID)。

  • 时间戳(Timestamps):记录文件的创建时间、修改时间和访问时间。

  • 数据块指针(Data Block Pointers):记录文件数据块的位置。

Linux系统是不认识文件名的,比如:不同的目录下,可以有多个文件名。系统识别文件靠的是文件编号(inode编号),多个文件名可以对应一个inode编号,这可以理解成文件的重命名。
 

文件在Block Group中的存储

其实我们不难发现,Linux文件系统是将文件的属性数据分开存放。

文件属性保存在inode table中,文件数据保存在Date blocks

我们通过下面的图片来理解:

对于一个较小的文件,inode可能直接包含若干个指针,每个指针指向一个数据块。而对于一个较大的文件,inode可能包含一级间接块指针,该一级间接块中存储了多个指向数据块的指针。

通过将数据块、inode表和相关的元数据组织在块组中,文件系统可以更高效地管理文件和目录。块组的划分可以提高文件系统的性能,因为每个块组都可以独立进行分配和管理,减少了全局搜索的开销。此外,块组的划分还可以提高文件系统的可靠性,因为文件系统可以在每个块组中保留一些备份的元数据,以防止数据损坏或丢失。

位图

文件是这样存储的,但是文件的属性和内容具体应该怎么存放呢?系统是如何判断那个数据是空的?这就要用到block bitmapinode bitmap

block bitmap用于查看Date blocks中哪个数据块是空的

node bitmap 用于查看inode table中哪个是空的

位图通俗来讲就是用每一个比特位代表两种状态

  • 比特位的位置:表示的就是 inode \ blocks 的编号的编号
  • 比特位的内容:0 \ 1 (0表示此位置的没占用,1表示已经占用)

所以只需要遍历位图,就可以找到空出来的inode,然后存放数据也能找到空的blocks,最后建立映射关系就可以了。

例如:

文件的创建、删除、恢复

将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过 touch 一个新文件来看看如何工作。
[root@localhost linux]# touch test
[root@localhost linux]# ls -i test
263466 test

为了更清楚的理解,我们通过图片来说明:

创建一个新文件主要有一下 4 个操作:
1. 存储属性
内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。
2. 存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据 复制到300,下一块复制到500,以此类推。
3. 记录分配情况
文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
4. 添加文件名到目录
新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文 件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。

文件的删除
创建一个文件是复杂的,删除是非常容易的,只需要将此文件在block group中占用的inode,blocks的编号内容从1设成0。这样就相当于完成了对文件的删除。

window的回收站其实并没有删除文件,只不过是将文件换到了一个叫做回收站的目录下,如果想真正意义的删除需要在回收站中再一次删除。

文件的恢复
文件删除不过是将其占用的inode,blocks的编号设为0,恢复怎么办呢?当然就是将编号从0在设为1。但是文件是一定可以被恢复嘛?不一定,很可能被删除了的文件的inode以及blocks都被其他的文件覆盖了。这种情况下,文件不能被恢复。

硬链接和软链接

硬链接

我们看到,真正找到磁盘上文件的并不是文件名,而是 inode 。 其实在 linux中可以让多个文件名对应于同一个 inode。换言之,硬链接就是同一个文件使用了多个别名。硬链接可由命令  link ln 创建。
[root@localhost linux]# touch abc 
[root@localhost linux]# ln abc def 
[root@localhost linux]# ls -1i abc def 
263466 abc
263466 def
  • abc和def的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode 263466 的硬连接数为2。
  • 我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则将对应 的磁盘释放。

软链接

软链接与硬链接不同,若文件用户数据块中存放的内容是另一文件的路径名的指向,则该文件就是软连接。软链接就是一个普通文件,只是数据块内容有点特殊。软链接有着自己的 inode 号以及用户数据块(如下图):
在Windows下,桌面的快捷图标都采用软链接的方式,我们可以右键属性查看:
对比软硬链接的区别,我们可以做出总结:
  1. 路径指向:软链接是一个特殊类型的文件,它包含了指向目标文件或目录的路径。硬链接则是一个指向目标文件或目录的直接链接,它们在文件系统中具有相同的inode号。

  2. 跨文件系统:软链接可以跨越不同的文件系统,即可以链接到其他文件系统上的文件或目录。而硬链接只能在同一个文件系统中创建链接。

  3. 文件类型:软链接本身是一个文件,它有自己的inode和文件大小。而硬链接则是指向目标文件或目录的直接链接,没有自己的inode和文件大小。

  4. 删除行为:如果删除目标文件,软链接仍然存在,但指向的目标文件将不可访问。而硬链接删除目标文件后,链接仍然存在,且仍然可以访问。

  5. 修改行为:修改软链接的目标文件会影响到软链接的指向,而修改目标文件不会影响到硬链接。

  6. 目标类型:软链接可以链接到文件或目录,而硬链接只能链接到文件。

总的来说,软链接更加灵活,可以跨越文件系统,可以链接到文件或目录,但它需要额外的空间来存储路径信息。而硬链接更加直接,没有额外的存储开销,但它只能在同一个文件系统中创建链接。

stat

我们可以通过 stat 指令来进一步查看文件的详细信息

[user@localhost linux]$ stat test.txt
  File: ‘test.txt’
  Size: 64        	Blocks: 8          IO Block: 4096   regular file
Device: fd01h/64769d	Inode: 1442471     Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1001/     user)   Gid: ( 1001/     user)
Access: 2023-09-03 11:33:33.213491610 +0800
Modify: 2023-09-03 11:33:31.837496768 +0800
Change: 2023-09-03 11:33:31.837496768 +0800
 Birth: -

  • File:文件名
  • Blocks :占用数据块个数
  • IO Block :IO数据块大小
  • regular file :文件类型
  • Device : 设备编号
  • Inode :inode编号
  • Links :硬链接数
  • Acess:文件的权限
  • uid和gid: 文件的所有者id
  • Access :最后访问时间
  • Modify :文件内容最后修改时间
  • Change :属性最后修改时间

动态库和静态库

动态库和静态库的基础知识

基本上,我们写的程序都是需要用到库的,具体使用到了某个库函数,链接时,会去库中找。

库一般有两种:

静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

查看一个程序链接了哪个库?静态链接或动态链接?

ldd 文件名

比如我写了一个程序,用ldd来查看它的链接关系:

[user@localhost linux]$ ldd test
	linux-vdso.so.1 =>  (0x00007ffe601d8000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fc1330df000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fc1334ad000)

可以看见这个程序链接的是动态库。

默认情况下,Linux是采用动态库的,如果我们想链接静态库,只需要在编译程序时带上 -static 选项:

[user@localhost linux]$ gcc -o static_test test -static

动态库和静态库的制作

测试程序

/add.h/
 #ifndef __ADD_H__
 #define __ADD_H__ 
 int add(int a, int b); 
 #endif // __ADD_H__
 /add.c/
 #include "add.h"
 int add(int a, int b)
 {
 return a + b;
 }
 /sub.h/
 #ifndef __SUB_H__
 #define __SUB_H__ 
 int sub(int a, int b); 
 #endif // __SUB_H__
 /add.c/
 #include "add.h"
 int sub(int a, int b)
 {
 return a - b;
 }
 ///main.c
 #include <stdio.h>
 #include "add.h"
 #include "sub.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));
 }

生成静态库

[root@localhost linux]# ls
add.c add.h main.c sub.c sub.h
[root@localhost linux]# gcc -c add.c -o add.o
[root@localhost linux]# gcc -c sub.c -o sub.o 
//生成静态库
[root@localhost linux]# ar -rc libmymath.a add.o sub.o 
//ar是gnu归档工具,rc表示(replace and create)
//查看静态库中的目录列表
[root@localhost linux]# ar -tv libmymath.a 
rw-r--r-- 0/0 1240 Sep 15 16:53 2023 add.o
rw-r--r-- 0/0 1240 Sep 15 16:53 2023 sub.o
//t:列出静态库中的文件
//v:verbose 详细信息
[root@localhost linux]# gcc main.c -L. -lmymath
//-L 指定库路径
//-l 指定库名
//测试目标文件生成后,静态库删掉,程序照样可以运行。

库搜索路径

从左到右搜索 -L 指定的目录。
由环境变量指定的目录 ( LIBRARY_PATH
由系统指定的目录 :/usr/lib   或者   /usr/local/lib
生成动态库
shared: 表示生成共享库格式
fPIC :产生位置无关码 (position independent code)
库名规则: libxxx.so
示例:
[root@localhost linux]# gcc -fPIC -c sub.c add.c
[root@localhost linux]# gcc -shared -o libmymath.so*.o 
[root@localhost linux]# ls 
add.c 
add.h 
add.o 
libmymath.so 
main.c 
sub.c 
sub.h 
sub.o
使用动态库
[root@localhost linux]gcc main.o -o main –L. -lhello
// l:链接动态库,只需要库名
// L:链接库所在的路径
运行动态库
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
ldconfifig 配置/etc/ld.so.conf.d/,ldconfifig更新
 [root@localhost linux]# cat /etc/ld.so.conf.d/bit.conf 
 /root/tools/linux
 [root@localhost linux]# ldconfig

总结

首先,我们复习了C语言文件的IO操作,从而引出了文件描述符fd,通过fd再次理解重定向。之后我们又学习了文件的储存机制,理解了inode,从而明白了文件的创建,删除,恢复。再理解了inode之后,学习软硬链接是简单的。最后我们学习了动态库和静态库的基础知识,并创建了属于我们自己的动,静态库。

以上就是今天要讲的内容,本文介绍了进程的相关概念。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值