对文件的理解:
1. 文件 = 文件内容 + 文件属性
2. 对文件的所有操作无外乎对文件内容操作或者对文件属性操作。
3. 文件在磁盘上存储,我们访问文件时,是谁在对文件进行访问呢?答案:进程。
4. 学习一门语言时,为什么我们重来没有听过有关文件类的系统调用接口?
a. 调用系统接口成本高,而且需要有一定程度对操作系统的理解,语言提供的文件类访问接口,本质是对操作系统的接口进行封装,使用比较简单。
b. 语言具有跨平台性,每个平台都有自己的操作系统,要让一种语言在不同的平台都能实现,实现方式:把所有平台的文件系统调用代码进行封装,条件编译,动态裁剪。
5. 显示器和磁盘一样也属于硬件,在显示器上打印数据和写入文件在磁盘上,广义上:二者没有本质区别,都可以进行写入。
6. Linux下一切皆文件,站在系统的角度,能够被input读取,或者能够被output写出的设备就叫做文件,狭义文件:普通磁盘文件,广义文件:显示器,磁盘,网卡,声卡,显卡,几乎所有外设
C语言文件接口
向文件中写入代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
FILE* f1= fopen("log.txt","w");
if(f1==NULL)
{
printf("打开文件失败\n");
exit(1);
}
const char* str1="hello fputs\n";
fputs(str1,f1);
const char* str2="hello fprintf\n";
fprintf(f1,"%s",str2);
const char* str3="hello fwrite\n";
fwrite(str3,strlen(str3),1,f1);
printf("打开文件成功\n");
fclose(f1);
return 0;
}
从文件中写出代码如下
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
FILE* fl=fopen("log.txt","r");
if(fl==NULL)
{
perror("open");
exit(1);
}
char name[64];
memset(name,'\0',sizeof(name));
fread(name,sizeof(name),1,fl);
name[sizeof(name)-1]='\0';
//按行读取
//fgets读取完成之后会自动在字符结尾+'\0'
fgets(name,sizeof(name),fl);
printf("%s\n",name);
fclose(fl);
return 0;
}
问:fopen的参数"w"是打开一个文件,如果文件不存在,默认会创建一个文件,那么这个文件会在什么路径下创建?
答:在这个进程的当前路径下(cwd),当一个进程运行起来时,会记录自己所处的工作路径。
注意:进程是知道自己所处的可执行程序路径(exe)的。
问:向一个文件中写入数据,fwrite第二个参数需要写入输入数据的大小,请问strlen函数计算要输入变量的大小时,需要进行+1嘛?
答:不需要,文件要的是有效数据,'\0'只是C语言的语法规则。
C语言打开文件的函数介绍:
FILE *fopen(const char *path, const char *mode);
第二个参数:
r Open text file for reading. The stream is positioned at the beginning of the file.
//只读,从一个文件的开始
r+ Open for reading and writing. The stream is positioned at the beginning of the file.
//可读可写,从一个文件的开始
w Truncate file to zero length or create text file for writing. The stream is positioned at the beginning of the file.
//只写,打开文件前会清空文件里面的内容,当要访问的文件不存在时,会创建这个文件,从文件的开始
w+ Open for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is positioned at the beginning of the file.
//可读可写,当要访问的文件不存在时,会创建这个文件,从文件开始
a Open for appending (writing at end of file). The file is created if it does not exist. The stream is positioned at the end of the file.
//只写,从文件结尾开始写,当要访问的文件不存在时,会创建这个文件。
a+ Open for reading and appending (writing at end of file). The file is created if it does not exist. The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file.
//可读可写,当要访问的文件不存在时,会创建这个文件,用于读取的初始文件位置位于文件的开头,但输出总是附加到文件的末尾
系统文件接口
标志位
原理代码如下:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
void func(int arg_t)
{
if(arg_t & ONE) printf("hello one\n");
if(arg_t & TWO) printf("hello two\n");
if(arg_t & THREE) printf("hello three\n");
}
int main()
{
func(ONE);
printf("------------------\n");
func(TWO);
printf("------------------\n");
func(THREE);
printf("------------------\n");
func(ONE|TWO);
printf("------------------\n");
func(ONE|THREE);
printf("------------------\n");
func(ONE|TWO|THREE);
return 0;
}
系统调用open()介绍
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int fd = open(const char *pathname, int flags);
int fd = open(const char *pathname, int flags, mode_t mode);
返回值:
fd:文件描述符
参数:
pathname:表示文件名
flags:
O_RDONLY:只读打开
O_WRONLY: 只写打开
O_RDWR: 读写打开
O_CREAT:若文件不存在时,创建它
O_TRUNC:打开文件前,清空文件
O_APPEND:追加
mode:表示创建文件时添加权限 如:0666
写文件:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);
int fd=open("myfile",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open");
return 1;
}
const char* name="zhangsan\n";
write(fd,name,sizeof(name));
close(fd);
return 0;
}
读文件:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
int fd =open("txt.txt",O_RDONLY);
if(fd<0)
{
perror("fopen");
return 1;
}
char str[64];
read(fd,str,sizeof(str));
printf("%s",str);
return 0;
}
open函数的返回值
认识系统调用和库函数的关系(实际上就是库函数调用了系统接口,并且进行一定程度的封装)
文件描述符fd
看以下代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
int fd1=open("log.txt1",O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd2=open("log.txt2",O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd3=open("log.txt3",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fd1:%d fd2:%d fd3:%d\n",fd1,fd2,fd3);
close(fd1);
close(fd2);
close(fd3);
return 0;
}
输出结果:fd1:3 fd2:4 fd3:5
问:为什么文件描述符是从3,4,5开始呢?0,1,2去哪里了?
答:操作系统会帮我们默认打开三个文件,文件指针分别是标准输入stdin,标准输出stdout,标准错误stderr,对应的文件描述符分别是0,1,2,对应的物理设备是:键盘,显示器,显示器。
我们需要知道c语言中fopen打开一个文件,fopen会有返回一个指针FILE*,那么这个FILE是个什么类型呢?
其实FIEL是一个结构体,里面包含了多种成员,其中就封装了成员fd,这是c标准库提供的。
证明如下:
#include<stdio.h>
int main()
{
printf("stdin:%d ",stdin->_fileno);
printf("stdout:%d ",stdout->_fileno);
printf("stderr:%d ",stderr->_fileno);
return 0;
}
输出结果:stdin:0 stdout:1 stderr:2
注意:也可以通过看C标准库结构体FILE的源代码来求证。
什么是文件描述符fd?
可执行程序加载进入内存,变成一个进程,进程要访问文件,必须先打开文件,一般而言一个进程,可以同时打开多个文件,文件如果要被进程访问,同样也需要被加载到内存中,当系统中存在大量的文件时,操作系统就必须将这些文件给先描述再组织。
用户层面打开一个文件进行相关操作的一些具体细节:
如:用户写代码时调用一个FILE* fl=fopen()之后,fopen底部会调用系统函数open,fl实际是一个结构体,里面包含文件操作符fd,写C语言代码时,调用的一些函数,底层封装的就是系统调用。
输出重定向(输入/追加重定向同理)
本质:实际上是在OS内部,更改文件描述符表中fd下标对应的内容指向。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main()
{
close(1);
int fd=open("tt.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open");
exit(1);
}
printf("%d ",fd);
char name[64]="i am a cat\n";
fprintf(stdout,"%s",name);
fflush(stdout);
close(fd);
return 0;
}
运行结果如下:1 iam a cat
细节原理图如下:
系统调用dup2
原理如下:
int dup2(int oldfd, int newfd);
返回值:调用成功返回新的fd,失败返回-1
使用方法:将oldfd下标对应文件指针,拷贝给newfd下标对应的文件指针
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int fd=open("tt.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open");
exit(1);
}
dup2(fd,1);
printf("%d",fd);
char name[64]="i am a cat\n";
fprintf(stdout,"%s",name);
fflush(stdout);
close(fd);
return 0;
}
理解Linux一切皆文件
缓冲区
例子:学生a给学生b送书,自己亲自去,写透模式WT,成本高,效率慢,给顺丰帮忙送书,效率快,成本低,写回模式WB。
1.什么是缓冲区?
就是一段内存空间。
2.为什么要用缓冲区?作用是什么?
提高整机效率,主要还是为了提高用户响应速度。
3.缓冲区在哪里?究竟是由谁提供?
文件指针FILE(结构体) 中,C语言提供的。
缓冲区刷新策略:
1. 立即刷新
2. 行刷新(行缓冲\n)
3. 满刷新(全缓冲)
特殊情况:1.用户强制刷新(fflush) 2.进程退出
注意:一般而言行刷新设备文件--显示器,全刷新设备文件--磁盘文件,显示器给用户看,一方面要考虑效率,一方面要考虑用户体验,所以,显示器是靠用户自己定义规则的。
关于缓冲区的认识:
所以设备都倾向于全缓冲,一旦缓冲区满了再刷新,这样可以减少IO操作,减少操作系统对外设的访问,从而提高效率。
注意以下代码,同样一个程序,向显示器打印显示4行文本,向磁盘上的文件打印时,文本显示变成7行,问为什么?
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
int fd =open("ll.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
dup2(fd,1);
fprintf(stdout,"hello fprintf\n");
printf("hello printf\n");
const char* s="hello fputs\n";
fputs(s,stdout);
const char* s1="hello write\n";
write(1,s1,strlen(s1));
fork();
return 0;
}
向显示器打印:
向磁盘文件打印:
解释如下:向显示器打印时,刷新策略是行刷新,当fork函数执行时,缓冲区里面的数据已经刷新了,但是重定向后,往磁盘文件中刷新,刷新策略是全刷新,当fork函数执行时,缓冲区中的数据还没刷新,此时数据属于父子进程共享,当父进程/子进程开始刷新写入时,就会发生写时拷贝,从而会向文件中打印俩次,又因为缓冲区在类FILE中,所以write函数只会刷新一次。
设计缓冲区代码(与C语言标准库存在差异):
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<assert.h>
#include<string.h>
#define NUM 1024
struct MYFILE_
{
int fd;
char buff[NUM];
int end;
};
typedef struct MYFILE_ MYFILE;
MYFILE* fopen_(const char* filename,const char* mode)
{
assert(filename);
assert(mode);
MYFILE* fl=NULL;
if(strcmp(mode,"r")==0)
{
}
else if(strcmp(mode,"r+")==0)
{
}
else if(strcmp(mode,"a")==0)
{
}
else if(strcmp(mode,"a+")==0)
{
}
else if(strcmp(mode,"w")==0)
{
int fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd>=0)
{
fl=(MYFILE*)malloc(sizeof(MYFILE));
memset(fl,0,sizeof(MYFILE));
fl->fd=fd;
}
}
else if(strcmp(mode,"w+")==0)
{
}
else
{
}
return fl;
}
void fputs_(const char* str,MYFILE* fl)
{
assert(str);
assert(fl);
strcpy(fl->buff+fl->end,str);
fl->end+=strlen(str);
if(fl->fd==1)
{
if(fl->buff[fl->end-1]=='\n')
{
write(fl->fd,fl->buff,fl->end);
fl->end=0;
}
}
}
void fflush_(MYFILE* fl)
{
assert(fl);
if(fl->end!=0)
{
write(fl->fd,fl->buff,fl->end);
syncfs(fl->fd);
fl->end=0;
}
}
void fclose_(MYFILE* fl)
{
assert(fl);
fflush_(fl);
close(fl->fd);
free(fl);
}
int main()
{
MYFILE* fl=fopen_("txt.txt","w");
if(fl==NULL)
{
perror("open file fail");
return 1;
}
fputs_("hello pig\n",fl);
fork();
fclose_(fl);
return 0;
}
minishell支持重定向操作:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define NUM 1024
#define OR 16
#define STR " "
//存储用户输入字符串指令和选项
char argv_t[NUM];
//保存每一个选项和指令的地址
char* args_t[OR];
//保存export环境变量地址
char env_t[16];
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define NONE_REDIR 0
int redir_status=NONE_REDIR;
char* CheckRedir(char* start)
{
assert(start);
char* end=start+strlen(start)-1;
while(end>=start)
{
if(*end=='>')
{
if(*(end-1)=='>')
{
redir_status=APPEND_REDIR;
*(end-2)='\0';
end++;
end++;
break;
}
redir_status=OUTPUT_REDIR;
*(end-1)='\0';
end++;
end++;
break;
}
else if(*end=='<')
{
redir_status=INPUT_REDIR;
*(end-1)='\0';
end++;
end++;
break;
}
else
{
end--;
}
}
if(end>=start)
{
return end;
}
else
{
return NULL;
}
}
int main()
{
extern char** environ;
//0.命令行解释器是一个常驻内存进程,不退出
while(1)
{
//1.打印提示信息
printf("[root@localhost myshell]# ");
fflush(stdout);
memset(argv_t,'\0',sizeof argv_t);
//2.获取用户键盘输入[输入的是各种指令和选项]
if(fgets(argv_t,sizeof argv_t,stdin)==NULL)
{
continue;
}
argv_t[strlen(argv_t)-1]='\0';
char *sep=CheckRedir(argv_t);
//3.命令行字符串解析
args_t[0]= strtok(argv_t,STR);
int ot=1;
if(strcmp(args_t[0],"ls")==0)
{
args_t[ot++]=(char*)"--color=auto";
}
if(strcmp(args_t[0],"ll")==0)
{
args_t[0]=(char*)"ls";
args_t[ot++]=(char*)"-l";
args_t[ot++]=(char*)"--color=auto";
}
while(args_t[ot++]=strtok(NULL,STR)) ;
//4.内置命令
//让父进程执行的命令,也叫做内建命令,本质其实就是shell中的一个函数调用
if(strcmp(args_t[0],"export")==0&&args_t[1]!=NULL)
{
//a3.重新建立一个全局数组变量用来存储环境变量的内容
strcpy(env_t,args_t[1]);
int ret=putenv(env_t);
//a1.检查在父进程是否导入成功
if(ret==0)
printf("%s export success\n",args_t[1]);
continue;
}
if(strcmp(args_t[0],"cd")==0)
{
if(args_t[1]!=NULL)
chdir(args_t[1]);
continue;
}
//5.fork()
pid_t id=fork();
if(id==0)
{
if(sep!=NULL)
{
int fd=-1;
switch(redir_status)
{
case INPUT_REDIR:
fd=open(sep,O_RDONLY);
dup2(fd,0);
break;
case OUTPUT_REDIR:
fd=open(sep,O_WRONLY|O_TRUNC|O_CREAT,0666);
dup2(fd,1);
break;
case APPEND_REDIR:
fd=open(sep,O_WRONLY|O_CREAT|O_APPEND,0666);
dup2(fd,1);
break;
default:
printf("bug?\n");
break;
}
}
//子进程
//a0.检查子进程中是否有环境变量MY_VAL
//printf("下面功能让子进程进行的\n");
//printf("child,MY_VAL:%s\n",getenv("MY_VAL"));
//print子进程f("child,PATH:%s\n",getenv("PATH"));
//a2.在子进程进行进程交换的时候,导入父进程中的环境变量
// execvpe(args_t[0],args_t,environ);
execvp(args_t[0],args_t);
exit(1);
}
int status=0;
pid_t pt=waitpid(id,&status,0);
if(pt>0)
printf("exit code:%d\n",WEXITSTATUS(status));
}
return 0;
}
close关闭fd之后,为什么文件内部没有数据?
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
close(1);
int fd=open("txt.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open");
exit(1);
}
重定向内容到txt.txt文件中,该文件的fd=1,所以打印内容是会将内容放在stdout的缓冲区中
不会遵循显示器的刷新策略,而是变成全缓冲
printf("hello:%d\n",fd);
fflush(stdout);
关闭fd对应的文件时,数据还在stdout缓冲区中,所以要将数据刷新出来,则需要fflush强制刷新
或者不关闭fd对应的文件,进程退出的时候也能刷新出来
close(fd);
return 0;
}
stdout和stderr的区别
概念:stdout和stderr对应的都是显示器文件,都是他们俩个是不同的,可以认为,同一个显示器文件,被打开了俩次。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
fprintf(stdout,"hello fprintf\n");
printf("hello printf\n");
fputs("hello fputs\n",stdout);
perror("hello perror");
return 0;
}
使用规则1:默认重定向只将stdout显示器内容打印到一个文件中
使用规则2:将stderr显示器内容重定向到一个文件中
使用规则3:将stdout和stderr显示器内容一起重定向到一个文件中
注意:一般而言如果程序运行可能有问题的时候,建议使用perror或者cerr打印,会将错误信息一起打印出来,如果程序没有问题则建议使用printf或者count打印。
perror模拟实现:
#include<stdio.h>
#include<string.h>
#include<errno.h>
void perror_(const char* s)
{
fprintf(stderr,"%s:%s\n",s,strerror(errno));
}
文件系统
我们知道一个程序进入内存就变成了一个进程,进程需要打开一些文件,这些文件被加载到内存就叫做内存文件,但是磁盘中还有大量的没有被打开的文件,那么操作系统是如何进行管理的呢?
站在操作系统的角度:如何对磁盘文件进行分门别类的存储,以便于更好的存取是主要的。
了解磁盘
优点:磁盘是一个永久性存储介质
缺点:磁盘是一个外设,并且还是一个机械设备,存取文件慢。
磁盘结构:盘片(多个),磁头(多个),伺服系统,音圈马达...
盘片:多个磁道(柱面),每个磁道有多个扇区,扇区上存储二进制数据。
CHS寻址方式:1.找面(对应是那个磁头)2.找磁道(柱面)3.找扇区
磁盘抽象(虚拟,逻辑)结构
得出结论:将数据存储到磁盘只需要将数据存储到该数组,找到磁盘特定扇区的位置,只需要找到数组特定的位置,对磁盘管理就变成了对数组管理。
inode
注意:虽然一个磁盘的基本单位是扇区(512字节),但是操作系统(文件系统)和磁盘进行IO的基本单位是4KB(8*512byte),是一个block大小,磁盘:块设备。
为什么呢?
1.太小了,有可能会导致操作系统和磁盘进行多次IO,进而导致效率降低
2.如果操作系统使用和磁盘一样的大小,万一磁盘基本大小变了,OS源代码要不要改呢?根本原因是使得软件和硬件解耦!
Super Block:文件系统的属性信息,记录的是一个分区的属性信息。
inode Table:inode是一个大小为128字节的空间,保存的是对应的文件的属性,Data Table表示该块组内,所有文件的inode集合,每个文件需要标识唯一性,每一个inode块,都要有一个inode编号,一般一个文件,一个inode,一个inode编号。
Data blocks:多个4KB(扇区*8)大小的集合,保存都是特定文件的内容。
Block Bitmap:假设有1000+个blocks,1000+个比特位:比特位和特定的block是一一对应的,其中比特位为1,代表该block被占用,否则表示可用。
inode Bitmap:假设有1000+个inode,1000+个比特位:比特位和特定的inode是一一对应的,其中比特位为1,代表该inode被占用,否则表示可用。
GDT:块组描述符,这个块组多大,已经使用了多少,有多少个inode,已经占用了多少个,还剩多少,一共有多少个block,使用了多少....
将块组分区目的在于:能够让一个文件的信息可追溯,可管理。
格式化:分割上面的内容,并且写入相关的管理数据,每一个块组都这样处理,整个分区就被写入文件系统信息。
注意:一个文件"只"对应一个inode属性节点,inode编号,一个文件可能会存在大量的block。
问:如何区分一个快组中的block属于哪一个文件呢?
答:inode中记录了一个文件的大多属性,其中一个属性为大小为15的数组,数组的前15个位置记录属于文件块组的编号,但是最后三个数组位置对应的block,block中可能会存着其他block的块号,前提是如果该文件的内容太多的话。
inode和文件名的关系
找到文件:inode编号->分区特定块组->inode->属性->内容
问:Linux中,inode属性不会存在文件名这一说法,那么文件名是存在在哪里?
答:存在于目录中,目录本质上也是一个文件,有属于自己的inode和data clock,data clock中存有文件名和inode编号,并且二者有映射关系,互为key值。
注意:分区格式化就是在磁盘写入文件系统。
面试题:问:一个磁盘还有空间,但是就是不能写入(内容/属性),为什么?
答:可能每一个分区中的块组,只有inode Table有空间,或者只有Data blocks有空间。
问:创建一个文件,文件系统做了什么?
问:删除文件,文件系统做了什么?
问:查看文件,系统做了什么?
软硬连接
区分软硬连接:有无独立的inode,软连接:是一个单独的文件,硬链接:不是一个单独的文件。
软连接
创建软连接文件:
特性:软连接的文件内容,是指向文件对应的路径。
应用:windows下的快捷方式。
硬链接(引用计数)
创建硬链接文件:
特性:在指定目录下,建立文件名和指定inode的映射关系,实际上就是多个文件名对应相同的inode。
应用:切换路径。
注意:当我们删除一个文件的时候,实际上并不是把这个文件的inode删除,而是将这个文件的inode引用计数--,当引用计数为0时,才删除inode对应的文件。
图中文件夹dir的硬连接数/引用计数为3,可以判断里面还包含一个文件,dir本身是一个文件,dir/.也是文件dir,dir内的一个文件夹内包含..也表示dir文件本身。
动静态库
静态库
原理:程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库
制作静态库并且将静态库和相应的头文件放入到hello文件夹中:
将原文件.c变成可重定向二进制目标文件.o再合并成一个静态库
libhello.a:myprint.o mymath.o
ar -rc libhello.a myprint.o mymath.o
myprint.o:myprint.c
gcc -c myprint.c -o myprint.o //绝对地址
mymath.o:mymath.c
gcc -c mymath.c -o mymath.o -std=c99
将静态库放在hello/lib/
hello/lib/libhello.a
将头文件放在hello/include/
hello/include/mymath.h
hello/include/myprint.h
使用hello文件夹的静态库和头文件:
第一种方式:
拷贝静态库和头文件到以下路径:
头文件gcc默认搜索路径是:/usr/include
库文件gcc默认搜索路径是:/usr/lib64 or /lib64
注意:自己写的库属于第三方库,既不属于操作系统本身,也不属于c语言,
所以搜索时需要加:gcc main.c -l hello,否则gcc找不到该静态库
第二种方式:
gcc main.c -I ./hello/include/ -L /hello/lib/ -l hello
查看静态库中的目录列表:
动态库
原理:程序在运行时才去链接动态库的代码,多个程序共享使用动态库的代码
制作动态库并且将动态库和相应的头文件放入到hello文件夹中:
将原文件.c变成可重定向二进制目标文件.o再合并成一个动态库
libhello.so:myprint.o mymath.o
gcc -shared myprint.o mymath.o libhello.so
myprint.o:myprint.c
gcc -c -fPIC myprint.c -o myprint.o //相对地址
mymath.o:mymath.c
gcc -c -fPIC mymath.c -o mymath.o -std=c99
将动态库放在hello/lib/
hello/lib/libhello.so
将头文件放在hello/include/
hello/include/mymath.h
hello/include/myprint.h
使用hello文件夹的动态库和头文件:
编译链接时,告诉gcc编译器动态库在哪里
第一种方式:
拷贝静态库和头文件到以下路径:
头文件gcc默认搜索路径是:/usr/include
库文件gcc默认搜索路径是:/usr/lib64 or /lib64
注意:自己写的库属于第三方库,既不属于操作系统本身,也不属于c语言,
所以搜索时需要加:gcc main.c -l hello,否则gcc找不到该静态库
第二种方式:
gcc main.c -I ./hello/include/ -L /hello/lib/ -l hello
程序运行加载时,告诉操作系统加载器动态库在哪里
第一种方式:将动态库放入系统路径下:/usr/lib64 or /lib64
第二种方式:向LD_LIBRARY_PATH导入环境变量(只在本次登录有效)
第三种方式:更改配置文件/etc/ld.so.conf.d/(一劳永逸)
第四种方式:软链接
动态库加载到内存并且和进程建立映射的过程
gcc编译链接时,使用动静态库的三种方式:
a. 如果只有静态库,只能针对该库进行静态链接
b. 如果动静态库同时存在,默认使用动态库
c. 如果动静态库同时存在,用户非要使用静态库 加-static
问:为什么要有库的存在?
答:站在使用库的角度:库的存在,可以大大减少我们的开发周期,提高软件的质量,站在写库的人的角度,简单,代码安全。