目录
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的具体结构如下图:
- 进程标识符(PID):用于唯一标识一个进程的整数值。
- 进程状态(state):表示进程的当前状态,如运行、等待、停止等。
- 进程优先级(prio):用于确定进程在调度时的优先级。
- 进程调度策略(policy):指定进程的调度策略,如实时调度或普通调度。
- 进程的父进程(parent):指向父进程的task_struct结构体。
- 进程的子进程链表(children):指向子进程的task_struct结构体链表。
- 进程的兄弟进程链表(sibling):指向同一父进程的其他子进程的task_struct结构体链表。
- 进程的文件描述符表(files):包含进程打开的文件描述符及其相关信息。
- 进程的运行时间统计(utime、stime):记录进程运行的用户态时间和内核态时间。
- 进程的地址空间(mm):用于管理进程的虚拟内存空间。
- 进程的信号处理器(signal):用于处理进程接收到的信号。
- 进程的调度信息(sched_info):记录进程的调度相关信息,如上次调度时间、调度次数等。
其中有一个文件描述符表(files),files本质上是一个结构体指针,指向了files_struct结构体:
- 引用计数(count):表示当前files_struct结构体的引用计数,用于管理结构体的生命周期。
- 文件描述符数组(fd_array):是一个指向file结构体指针的数组,用于存储打开的文件描述符和相关信息。
- 文件描述符的最大限制(max_fds):表示文件描述符数组的最大长度,即最多可以打开的文件描述符数量。
- 文件描述符位图(open_fds):是一个位图,用于快速检查文件描述符是否已经被使用。
- 文件描述符表锁(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表。
每个块组通常包含以下几个主要组件:
Super Block(超级块):存储文件系统的整体信息,如文件系统的大小、块大小、inode的数量等。它是文件系统的核心组成部分,用于恢复文件系统的一致性和完整性。
Group Descriptor Table(组描述符表):存储文件系统中每个数据块组的信息,包括数据块组的起始地址、数据块的数量、空闲数据块的数量等。
Block Bitmap(块位图):用于记录文件系统中每个数据块的使用情况,即哪些数据块是已经分配给文件或目录,哪些是空闲的可用于分配的。
Inode Bitmap(inode位图):用于记录文件系统中每个inode的使用情况,即哪些inode是已经分配给文件或目录,哪些是空闲的可用于分配的。
Inode Table(inode表):存储文件系统中所有文件和目录的元数据信息,如文件大小、权限、所有者等。
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 bitmap和inode bitmap
block bitmap用于查看Date blocks中哪个数据块是空的
node bitmap 用于查看inode table中哪个是空的
位图通俗来讲就是用每一个比特位代表两种状态
- 比特位的位置:表示的就是 inode \ blocks 的编号的编号
- 比特位的内容:0 \ 1 (0表示此位置的没占用,1表示已经占用)
所以只需要遍历位图,就可以找到空出来的inode,然后存放数据也能找到空的blocks,最后建立映射关系就可以了。
例如:
文件的创建、删除、恢复
[root@localhost linux]# touch test
[root@localhost linux]# ls -i test
263466 test
为了更清楚的理解,我们通过图片来说明:
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都被其他的文件覆盖了。这种情况下,文件不能被恢复。
硬链接和软链接
硬链接
[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号。
跨文件系统:软链接可以跨越不同的文件系统,即可以链接到其他文件系统上的文件或目录。而硬链接只能在同一个文件系统中创建链接。
文件类型:软链接本身是一个文件,它有自己的inode和文件大小。而硬链接则是指向目标文件或目录的直接链接,没有自己的inode和文件大小。
删除行为:如果删除目标文件,软链接仍然存在,但指向的目标文件将不可访问。而硬链接删除目标文件后,链接仍然存在,且仍然可以访问。
修改行为:修改软链接的目标文件会影响到软链接的指向,而修改目标文件不会影响到硬链接。
目标类型:软链接可以链接到文件或目录,而硬链接只能链接到文件。
总的来说,软链接更加灵活,可以跨越文件系统,可以链接到文件或目录,但它需要额外的空间来存储路径信息。而硬链接更加直接,没有额外的存储开销,但它只能在同一个文件系统中创建链接。
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/lib2 、更改 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
[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相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!