当前位置: 首页 > news >正文

分析网站规划建设目标建设网站赚钱吗

分析网站规划建设目标,建设网站赚钱吗,wordpress 4.5.3中固定链接,网站外链建设大揭秘前言 本期分享基础IO的知识#xff0c;主要有#xff1a; 复习C语言文件操作文件相关的系统调用文件描述符fd理解Linux下一切皆文件缓冲区文件系统软硬链接动静态库的理解和制作动静态编译 博主水平有限#xff0c;不足之处望请斧正#xff01; C语言文件操作 #再谈文件…前言 本期分享基础IO的知识主要有 复习C语言文件操作文件相关的系统调用文件描述符fd理解Linux下一切皆文件缓冲区文件系统软硬链接动静态库的理解和制作动静态编译 博主水平有限不足之处望请斧正 C语言文件操作 #再谈文件 先回忆文件相关知识结合已学操作系统知识来构建共识 文件 内容属性 空文件也占空间属性要占文件操作 对属性/内容的操作 标定一个文件必须用 文件路径 文件名 的方式具有唯一性 没有指明文件路径时默认在当前路径进程的cwd进行文件访问 操作文件的一定是进程 程序只有载入内存运行才会执行文件操作文件只有被“打开”后才能访问 “打开”用户进程调用接口 操作系统“打开”文件所以文件可分为 被打开的文件未被打开的文件 重点 操作文件的一定是进程程序只有载入内存运行文件操作才会被执行文件只有被“打开”调用系统接口系统来打开文件之后才能操作 有了这些共识我们可以得出结论 文件操作 进程 操作 被打开文件的内容/属性 C语言文件接口使用 文件接口使用 写 #include stdio.h#define FILE_NAME log.txtint main() {//opt: r 读不存在出错 / w写不存在创建存在则自动清空数据 // r 读写不存在创建 / w读写不存在出错// a 追加 / aFILE* pf fopen(FILE_NAME, w);if(NULL pf){perror(fopen);return 1;}int cnt 5;while(cnt){fprintf(pf, %s: %d\n, Hello Bacon!, cnt--);}fclose(pf);return 0; }[baconVM-12-5-centos 2]$ make gcc -o test test.c -stdc99 [baconVM-12-5-centos 2]$ ./test [baconVM-12-5-centos 2]$ ls log.txt makefile test test.c [baconVM-12-5-centos 2]$ cat log.txt Hello Bacon!: 5 Hello Bacon!: 4 Hello Bacon!: 3 Hello Bacon!: 2 Hello Bacon!: 1读 #include stdio.h#define FILE_NAME log.txtint main() {FILE* pf fopen(FILE_NAME, r);if(NULL pf){perror(fopen);return 1;}char buf[64];//fgets默认添加\0因为C语言字符串以\0结尾而fgets获取的是字符串//为什么要-1以防极端情况fgets想添加\0添不上while(fgets(buf, sizeof(buf) - 1, pf) ! NULL){printf(%s, buf); //log.txt内已经带了\n}fclose(pf);return 0; }Hello Bacon!: 5 Hello Bacon!: 4 Hello Bacon!: 3 Hello Bacon!: 2 Hello Bacon!: 1用都会用但感觉差口气儿。 理解文件接口 首先提出一个问题C语言有文件接口其他语言有吗 一定有的。 为什么因为文件在磁盘磁盘是硬件只有操作系统能访问硬件。 那我们又能知道语言层面的文件接口都必须调用系统接口。 为什么因为操作系统对下管理硬件对上通过提供系统调用的方式给用户良好的运行环境任何人想访问硬件都得过操作系统这关它提供系统调用我们就能访问不提供就不能。语言想进行文件操作也只能通过系统调用。 任何人想访问硬件只能通过系统调用学习文件接口要学习不变的系统调用 文件相关的系统调用 open NAMEopen, creat - open and possibly create a file or deviceSYNOPSIS#include sys/types.h#include sys/stat.h#include fcntl.hint open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode); RETURN VALUEopen() and creat() return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).先看第二个open 作用打开或可能创建一个文件或设备参数 pathname要打开的文件路径flags标记位通过比特位传递选项mode权限 返回值成功返回文件描述符file descriptor失败返回-1 为什么有两个open 前者是不需要创建文件时用的不需要给默认权限后者是需要创建文件时用的需要给默认权限mode #通过比特位传递选项 #define ONE (1 0) #define TWO (1 1) #define THREE (1 2) #define FOUR (1 3)void bitOption(int flags) {if(flags ONE) printf(ONE\n);if(flags TWO) printf(TWO\n);if(flags THREE) printf(THREE\n);if(flags FOUR) printf(FOUR\n); }int main() {bitOption(ONE | TWO | FOUR);return 0; }对于open的flags有几个宏 O_RDONLY 只读O_WRONLY 只写O_RDWR 读或写O_CREAT 文件不存在则创建文件O_TRUNC 文件存在则清空文件内容O_APPEND 追加 close NAMEclose - close a file descriptorSYNOPSIS#include unistd.hint close(int fd);作用关掉文件描述符对应的文件参数 fd 返回值成功返回0失败返回-1 打开并关闭文件 int main() {int fd open(FILE_NAME, O_WRONLY);if(fd -1 ){perror(open);return 1;}close(fd);return 0; }[baconVM-12-5-centos 2]$ ./test open: No such file or directory怎么找不到呢以写方式打开默认不是会创建吗 我们不能把库函数那套照搬到系统调用上“自动创建”是C库函数自己做的事。 int fd open(FILE_NAME, O_WRONLY | O_CREAT);给上了O_CREATE我们现在要创建文件却没给默认权限会怎么样 [baconVM-12-5-centos 2]$ ./test [baconVM-12-5-centos 2]$ ll total 20 -rw--ws--T 1 bacon bacon 0 Jan 20 19:28 log.txt -rw-rw-r-- 1 bacon bacon 68 Jan 20 10:48 makefile -rwxrwxr-x 1 bacon bacon 8456 Jan 20 19:25 test -rw-rw-r-- 1 bacon bacon 1354 Jan 20 19:25 test.c根据权限也能知道log.txt现在完全就是乱的。 正确用法 int fd open(FILE_NAME, O_WRONLY | O_CREAT, 0666);[baconVM-12-5-centos 2]$ ./test [baconVM-12-5-centos 2]$ ll total 20 -rw-rw-r-- 1 bacon bacon 0 Jan 20 19:33 log.txt -rw-rw-r-- 1 bacon bacon 68 Jan 20 10:48 makefile -rwxrwxr-x 1 bacon bacon 8456 Jan 20 19:33 test -rw-rw-r-- 1 bacon bacon 1360 Jan 20 19:33 test.clog.txt经过umask得到正确的权限。若我们不想要默认的umask可以自己设置 NAMEumask - set file mode creation maskSYNOPSIS#include sys/types.h#include sys/stat.hmode_t umask(mode_t mask);... umask(0); ...[baconVM-12-5-centos 2]$ ./test [baconVM-12-5-centos 2]$ ll total 20 -rw-rw-rw- 1 bacon bacon 0 Jan 20 19:40 log.txt -rw-rw-r-- 1 bacon bacon 68 Jan 20 10:48 makefile -rwxrwxr-x 1 bacon bacon 8512 Jan 20 19:39 test -rw-rw-r-- 1 bacon bacon 1374 Jan 20 19:39 test.cwrite NAMEwrite - write to a file descriptorSYNOPSIS#include unistd.hssize_t write(int fd, const void *buf, size_t count);作用向文件描述符写入参数 fdbuf 要被写入的数据count 要写入的数据的字节个数 返回值返回写入的字节个数 buf为什么是void*数据类型是语言的事操作系统眼里都是二进制 向文件写入不存在则创建存在则清空 int main() {umask(0);int fd open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);if(fd -1){perror(open);return 1;}int cnt 5;char outBuf[64];while(cnt){sprintf(outBuf, %s %d\n, Hello Bacon, cnt--);//第三个参数要不要1把\0也写入文件不用\0结尾是C语言的事和文件无关write(fd, outBuf, strlen(outBuf)); }close(fd);return 0; }[baconVM-12-5-centos 2]$ ./test [baconVM-12-5-centos 2]$ ll total 24 -rw-rw-rw- 1 bacon bacon 70 Jan 20 21:32 log.txt -rw-rw-r-- 1 bacon bacon 68 Jan 20 10:48 makefile -rwxrwxr-x 1 bacon bacon 8664 Jan 20 21:32 test -rw-rw-r-- 1 bacon bacon 1678 Jan 20 21:32 test.c [baconVM-12-5-centos 2]$ cat log.txt Hello Bacon 5 Hello Bacon 4 Hello Bacon 3 Hello Bacon 2 Hello Bacon 1open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666); fopen(FILE_NAME, w); 追加 int fd open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);[baconVM-12-5-centos 2]$ ./test [baconVM-12-5-centos 2]$ ./test [baconVM-12-5-centos 2]$ ./test [baconVM-12-5-centos 2]$ cat log.txt Hello Bacon 5 Hello Bacon 4 Hello Bacon 3 Hello Bacon 2 Hello Bacon 1 Hello Bacon 5 Hello Bacon 4 Hello Bacon 3 Hello Bacon 2 Hello Bacon 1 Hello Bacon 5 Hello Bacon 4 Hello Bacon 3 Hello Bacon 2 Hello Bacon 1 Hello Bacon 5 Hello Bacon 4 Hello Bacon 3 Hello Bacon 2 Hello Bacon 1read NAMEread - read from a file descriptorSYNOPSIS#include unistd.hssize_t read(int fd, void *buf, size_t count);作用从文件描述符对应的文件读参数 fdbuf 被读入数据存放的地方count 要读入数据的字节个数 返回值返回成功读入的字节个数返回0代表读到了文件结尾 从文件读 int main() {umask(0);int fd open(FILE_NAME, O_RDONLY);if(fd -1 ){perror(open);return 1;}char buf[1024]; b ssize_t read_ret read(fd, buf, sizeof(buf) - 1);if(read_ret 0) buf[read_ret] 0; //我们读入字符串后在结尾加上\0printf(%s\n, buf);close(fd);return 0; }[baconVM-12-5-centos 2]$ ./test Hello Bacon 5 Hello Bacon 4 Hello Bacon 3 Hello Bacon 2 Hello Bacon 1 Hello Bacon 5 Hello Bacon 4 Hello Bacon 3 Hello Bacon 2 Hello Bacon 1 Hello Bacon 5 Hello Bacon 4 Hello Bacon 3 Hello Bacon 2 Hello Bacon 1 Hello Bacon 5 Hello Bacon 4 Hello Bacon 3 Hello Bacon 2 Hello Bacon 1 #文件描述符fd 我们说“文件操作 进程 操作 被打开文件”那就有个问题当打开很多文件的时候操作系统如何管理这些被打开的文件一定是四步走抽象、具象、组织、操作。我们说的文件操作其实就是这四步中的“操作”。但其他我们还不清楚 现在来谈谈文件的抽象、具象和组织。 文件的抽象 struct file{//文件的大部分属性 }文件的具象和组织 先来看一个现象 #define FILE_NAME(num) log.txt#numint main() {int fd1 open(FILE_NAME(1), O_WRONLY | O_CREAT, 0666);int fd2 open(FILE_NAME(2), O_WRONLY | O_CREAT, 0666);int fd3 open(FILE_NAME(3), O_WRONLY | O_CREAT, 0666);int fd4 open(FILE_NAME(4), O_WRONLY | O_CREAT, 0666);printf(fd1 %d\n, fd1);printf(fd2 %d\n, fd2);printf(fd3 %d\n, fd3);printf(fd4 %d\n, fd4);close(fd1);close(fd2);close(fd3);close(fd4);return 0; }[baconVM-12-5-centos 2]$ ./mytest fd1 3 fd2 4 fd3 5 fd4 6从3开始 为什么fd是从3开始 既然是连续的整数“0、1、2”哪去了 我们研究fd不妨也研究一下它的一种封装 调用C语言的文件接口时必须用FILE* pf调用系统调用时必须用int fd。而且前者底层脱不开后者那这库函数想玩儿必须用到fd即FILE内一定有一个字段是fd 很巧的是默认打开的三个标准输入输出流恰好是FILE*类型。 NAMEstdin, stdout, stderr - standard I/O streamsSYNOPSIS#include stdio.hextern FILE *stdin;extern FILE *stdout;extern FILE *stderr;其中会不会有什么联系 #define FILE_NAME(num) log.txt#numint main() {printf(fd of stdin %d\n, stdin-_fileno); //获取FILE结构体内类似fd的字段printf(fd of stdout %d\n, stdout-_fileno);//获取FILE结构体内类似fd的字段printf(fd of stderr %d\n, stderr-_fileno);//获取FILE结构体内类似fd的字段int fd1 open(FILE_NAME(1), O_WRONLY | O_CREAT, 0666);int fd2 open(FILE_NAME(2), O_WRONLY | O_CREAT, 0666);int fd3 open(FILE_NAME(3), O_WRONLY | O_CREAT, 0666);int fd4 open(FILE_NAME(4), O_WRONLY | O_CREAT, 0666);printf(fd1 %d\n, fd1);printf(fd2 %d\n, fd2);printf(fd3 %d\n, fd3);printf(fd4 %d\n, fd4);close(fd1);close(fd2);close(fd3);close(fd4);return 0; }fd of stdin 0 fd of stdout 1 fd of stderr 2 fd1 3 fd2 4 fd3 5 fd4 6果然“0、1、2”分别对应默认打开的三个标准输入输出流stdin、stdout、stderr 。 连续整数 我们也发现被打开文件的描述符是连续的整数这不禁让人想到数组的下标啊。 文件的具象和组织具体如图所示 fd的本质就是数组的下标是fd_array的下标。 将以上内容串联下就能知道OS是怎么管理文件的了。 文件的管理 抽象、具象struct file有文件的大部分属性 组织task_struct对象内有struct files_struct* files指向struct files_struct对象 其中又有**struct file* fd_array[]每一个元素都是一个FILE指针它构建了进程(task_struct)和被打开文件(struct file)的映射关系它的下标被称作文件描述符fd** 操作open、close、write、read… fd的分配规则 实践出真知。 int main() {close(0);int fd open(log.txt, O_WRONLY | O_CREAT, 0666);printf(fd %d\n, fd);close(fd);return 0; }[baconVM-12-5-centos 3]$ ./mytest fd 0int main() {close(2);int fd open(log.txt, O_WRONLY | O_CREAT, 0666);printf(fd %d\n, fd);close(fd);return 0; }[baconVM-12-5-centos 3]$ ./mytest fd 2分配规则从小到大分配最小的未被占用fd。 再关1试试 int main() {close(1);int fd open(log.txt, O_WRONLY | O_CREAT, 0666);printf(fd %d\n, fd);close(fd);return 0; }[baconVM-12-5-centos 3]$ ./mytest [baconVM-12-5-centos 3]$ 关掉fd为1的文件就不打印结果了为什么 printf本质其实就是写入到fd为1的stdout内然后读取stdout输出到显示器上。需要注意 系统默认stdout的fd为1。 也就是即使我们把fd为1的stdout关掉系统仍然会从fd为1的文件中读取并输出到显示器。 按这么说关掉fd为1的stdout我再打开log.txt会从小到大找最小的未被占用fd即 1那我的log.txt岂不是成了系统眼里的stdout 那也代表printf会向我的log.txt写入 [baconVM-12-5-centos 3]$ ./mytest [baconVM-12-5-centos 3]$ cat log.txt1 [baconVM-12-5-centos 3]$ 文件里没有啊其实这跟缓冲区有关我们刷新一下就可以看到。 int main() {close(1);int fd open(log.txt, O_WRONLY | O_CREAT, 0666);printf(fd %d\n, fd);fflush(stdout);close(fd);return 0; }[baconVM-12-5-centos 3]$ ./mytest [baconVM-12-5-centos 3]$ cat log.txt fd 1本来是输出到stdout向stdout写入一通操作后输出到了log.txt向log.txt写入这就是重定向。 #重定向 是什么 改变向文件输入/输出的“方向”。 本质就是修改struct file* fd_array中的元素即更改某fd对应的FILE*指针。 *1这个fd对应的指向原本是stdout经过close和open最终指向了log.txt1。 分类 输出重定向 追加重定 向 输入重定向 但是close open有点挫 有没有别的办法进行重定向有的 怎么做 可以通过一个系统调用dup2。 NAMEdup, dup2, dup3 - duplicate a file descriptorSYNOPSIS#include unistd.hint dup(int oldfd);int dup2(int oldfd, int newfd);#define _GNU_SOURCE /* See feature_test_macros(7) */#include fcntl.h /* Obtain O_* constant definitions */#include unistd.hint dup3(int oldfd, int newfd, int flags);其中dup2是我们最常用的 dup2() makes newfd be the copy of oldfd, closing newfd first if necessary作用让newfd对应元素变成oldfd对应元素的一份拷贝fd_arrays[newfd] fd_arrays[oldfd]返回值成功返回newfd失败返回-1 #define FILE_NAME log.txtint main() {//原本输出写入到stdout现在要输出写入到我自己的log.txtint fd open(FILE_NAME, O_WRONLY | O_CREAT, 0666);int dup2_ret dup2(fd, 1);assert(dup2_ret ! -1);printf(fd %d\n, fd);fflush(stdout);close(fd);return 0; }[baconVM-12-5-centos 3]$ ./mytest [baconVM-12-5-centos 3]$ ls log.txt makefile mytest mytest.c [baconVM-12-5-centos 3]$ cat log.txt fd 3fd_array[1] fd_array[fd] 以上是输出重定向其他两个重定向也很简单。 追加重定向 #define FILE_NAME log.txtint main() {int fd open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);printf(fd %d\n, fd);fflush(stdout);close(fd);return 0; }[baconVM-12-5-centos 3]$ cat log.txt fd 3 [baconVM-12-5-centos 3]$ ./mytest [baconVM-12-5-centos 3]$ ./mytest [baconVM-12-5-centos 3]$ ./mytest [baconVM-12-5-centos 3]$ cat log.txt fd 3 fd 3 fd 3 fd 3输入重定向 int main() {//输入重定向原本从stdin输入读现在从我自己的log.txt输入读int fd open(FILE_NAME, O_RDONLY, 0666);if(fd 0){perror(open);return -1;}dup2(fd, 0);char line[64];while(1){printf( );if(fgets(line, sizeof(line), stdin) NULL) break;printf(%s, line);}close(fd);return 0; } [baconVM-12-5-centos 3]$ cat log.txt hello world! Im Bacon! [baconVM-12-5-centos 3]$ ./mytest hello world!Im Bacon![baconVM-12-5-centos 3]$ #把重定向加入shell shell的原理是程序替换所以重定向的工作一定是子进程来做而父进程只需要规定子进程如何重定向。 实现一个redirCheck函数获取命令中的重定向信息交给子进程来真正重定向。 redirCheck实现思路 全局变量redirType 保存重定向的类型全局变量redirFile保存要重定向至的文件名父进程调用redirCheck函数来获取前两者子进程也能共享到 并处理输入的命令 处理命令将重定向标识置空 arg交给shell处理后面的文件名保存不需要交给shell重定向标识置空后不会读取到文件名了 #include stdio.h #include ctype.h #include unistd.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include sys/wait.h #include assert.h #include string.h #include errno.h#define NUM 1024 #define OPT_NUM 32#define NONE_REDIR 0 #define INPUT_REDIR 1 #define APPEND_REDIR 2 #define OUTPUT_REDIR 3#define trimSpace(start) do{\while(isspace(*start)) start; \ }while(0)char lineCommand[NUM]; char* myargv[OPT_NUM]; int lastCode 0; int lastSig 0;//重定向信息 int redirType NONE_REDIR; char* redirFile NULL;//重定向检查 //1. 获取重定向信息重定向类型和重定向的文件 //2. 处理命令将arg和文件名分离arg需要给shell用文件名保存即可 //ls -a -l myfile ls -a -l \0 myfile void redirCheck(char* commands) {assert(commands); char* start commands;char* end commands strlen(commands);while(start end){if(*start ) //确定重定向类型{*start \0; //切割arg和文件start;if(*start ) //追加重定向{redirType APPEND_REDIR;start; //跳过第二个}else //输出重定向{redirType OUTPUT_REDIR; //获取重定向类型}trimSpace(start); //宏函数过滤空格redirFile start; //获取重定向文件break;}else if(*start ){*start \0;start;trimSpace(start);redirType INPUT_REDIR;redirFile start;break;}else {start;}} }int main() {while(1){redirType NONE_REDIR;redirFile NULL;printf([%s%s]# , getenv(USER), getenv(HOSTNAME));fflush(stdout);char* ret fgets(lineCommand, sizeof(lineCommand) - 1, stdin); assert(ret ! NULL); lineCommand[strlen(lineCommand) - 1] 0;redirCheck(lineCommand);myargv[0] strtok(lineCommand, );int i 1;while(myargv[i] strtok(NULL, ));if(myargv[0] ! NULL strcmp(myargv[0], cd) 0){if(myargv[1] ! NULL) chdir(myargv[1]);continue;}if(myargv[0] ! NULL myargv[1] ! NULL strcmp(myargv[0], echo) 0){if(strcmp(myargv[1], $?) 0){printf(exitCode %d | exitSig %d\n, lastCode, lastSig);}else {printf(%s\n, myargv[1]);}continue;}pid_t id fork();assert(id ! -1);if(id 0){//重定向switch(redirType){case NONE_REDIR:break;case INPUT_REDIR:{int fd open(redirFile, O_RDONLY);if(fd 0){perror(open redirFile);exit(errno);}dup2(fd, 0);}break;case OUTPUT_REDIR:case APPEND_REDIR: {int flags O_WRONLY | O_CREAT; if(redirType APPEND_REDIR) flags | O_APPEND;else flags | O_TRUNC;int fd open(redirFile, flags, 0666);if(fd 0){perror(open redirFile);exit(errno);}dup2(fd, 1);}break;default:printf(something wrong...\n);break;}execvp(myargv[0], myargv);exit(1);}int status 0;pid_t wait_ret waitpid(id, status, 0);assert(wait_ret 0);lastCode (status 8) 0xFF;lastSig status 0x7F;}return 0; }[baconVM-12-5-centos myshell]$ ./myshell [baconVM-12-5-centos]# ls -a log.txt [baconVM-12-5-centos]# ls log.txt makefile myshell myshell.c [baconVM-12-5-centos]# cat log.txt . .. log.txt makefile myshell myshell.c [baconVM-12-5-centos]# ls -a -l log.txt [baconVM-12-5-centos]# cat log.txt . .. log.txt makefile myshell myshell.c total 40 drwxrwxr-x 2 bacon bacon 4096 Jan 26 20:29 . drwxrwxr-x 6 bacon bacon 4096 Jan 26 17:49 .. -rw-rw-r-- 1 bacon bacon 40 Jan 26 20:29 log.txt -rw-rw-r-- 1 bacon bacon 115 Jan 19 10:26 makefile -rwxrwxr-x 1 bacon bacon 13824 Jan 26 20:29 myshell -rw-rw-r-- 1 bacon bacon 4650 Jan 26 20:29 myshell.c [baconVM-12-5-centos]# cat log.txt . .. log.txt makefile myshell myshell.c total 40 drwxrwxr-x 2 bacon bacon 4096 Jan 26 20:29 . drwxrwxr-x 6 bacon bacon 4096 Jan 26 17:49 .. -rw-rw-r-- 1 bacon bacon 40 Jan 26 20:29 log.txt -rw-rw-r-- 1 bacon bacon 115 Jan 19 10:26 makefile -rwxrwxr-x 1 bacon bacon 13824 Jan 26 20:29 myshell -rw-rw-r-- 1 bacon bacon 4650 Jan 26 20:29 myshell.c子进程重定向会不会影响父进程的struct files_struct 不影响struct files_struct是进程相关的数据结构而进程间具有独立性。 需要注意的是创建子进程时并不会为子进程把所有已打开文件再拷贝一份因为进程管理是进程管理文件管理是文件管理。 子进程执行程序替换会不会影响因重定向打开的文件 不影响程序替换只是用磁盘上的代码和数据替换进程原来的代码和数据 跟这些内核数据结构没有任何关系。 #引用计数 肯定存在多个进程同时打开一个文件的情况这时如果某个进程退出了对文件有什么影响 这就要提到引用计数。对于某个文件file其引用计数ref_count表示的是有多少地方指向自己也就是引用自己。 3个进程引用自己ref_count 3文件不关闭 此时其中的一个进程退出… 2个进程引用自己ref_count 2文件不关闭 再有一个退出… ref_count 1 文件不关闭 再有一个退出… ref_count 0文件关闭。 比如某个进程closeOS只是会--ref_count。 从中我们可以体会多个进程打开文件是怎么个样子了。 如何理解Linux下一切皆文件 拿硬件举例 我们通过冯诺依曼体系结构可以知道想要操作键盘磁盘这类外设的数据必须读取到内存处理完后再写入回去这中操作也叫IO。操作系统要管理硬件就需要抽象、具象、组织、操作 抽象方面各个硬件分别有自己的抽象操作方面各个硬件为了能操作硬件的数据都要提供读写方法若某个设备不需要读/写方法可以读/写方法内可以什么都不做*这些硬件的读写方法其实就在其驱动程序里 对于这些硬件操作系统怎么看全部看做文件属性都按需抽象出来管你来的是哪个硬件属性该填的填至于你们的读写你自己提供我只需要用一个指针找到你提供的用现成的 最后一步全部看做文件也就是图中struct file这一层就叫做**VFS(virtual file system)**虚拟文件系统。很像是一种封装虚拟文件系统让我们不需要关心底层硬件管理的各种不同细节也能让上层的软件能够用单一的方式来跟底层不同的硬件沟通。 为什么说Linux下一切皆是文件因为有VFS来“统一度量衡”。 我们可以看看源码的细节来验证我们上面的说法。 struct file 内是有指针指向文件操作方法的 struct file {...const struct file_operations *f_op;... }struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, struct dentry *, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **); };其他我们没学过 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);这两个我们是知道的。 #缓冲区 先来看一个现象。 int main() {//Cprintf(Im printf\n);fprintf(stdout, Im fprintf\n);fputs(Im fputs\n, stdout);//system callconst char* msg Im write\n;write(1, msg, strlen(msg));return 0; }[baconVM-12-5-centos 5-buffer]$ ./test Im printf Im fprintf Im fputs Im write这都没问题但是…… int main() {//Cprintf(Im printf\n);fprintf(stdout, Im fprintf\n);fputs(Im fputs\n, stdout);//system callconst char* msg Im write\n;write(1, msg, strlen(msg));fork();return 0; }[baconVM-12-5-centos 5-buffer]$ ./test Im printf Im fprintf Im fputs Im write [baconVM-12-5-centos 5-buffer]$ ./test log.txt [baconVM-12-5-centos 5-buffer]$ cat log.txt Im write Im printf Im fprintf Im fputs Im printf Im fprintf Im fputsfork后打印是正常的但是重定向怎么不对劲 我们得先了解缓冲区。 是什么 一段内存。 为什么 我们可以通过寄特产的例子来理解 广东人张三和东北人李四是网友一天张三想寄点特产给李四两种方案 自己骑自己的单车自己送花上几个月放到楼下顺丰快递点顺丰送只需要下个楼的时间 自然是后者好因为前者太浪费自己的时间。快递行业的意义其实就在其中节省发送者的时间。 把寄特产换成写文件 张三就是进程要把数据从内存输出到磁盘李四就是文件在磁盘中等待被写入 张三要寄特产即进程要输出到磁盘时 进程自己访问就相当于自己骑单车从广东到东北拷贝到缓冲区让缓冲区定期刷新就相当于找顺丰帮忙寄 那么为什么要有缓冲区也明了了节省进程访问外设的时间。 不对啊拷贝到缓冲区我可没干这事啊 与其说我们用的IO接口如fwrite是写入到文件的函数不如说它们是拷贝函数——从进程拷贝到“缓冲区”或外设中。 还有个问题我把特产给顺丰他什么时候给我送呢 我把数据拷贝到缓冲区它什么时候给我刷新呢 缓冲区的刷新策略 缓冲区是为了节省进程时间那我们看一坨数据怎么刷新到外设最能体现缓冲区的意义 少量多次 or 多量少次 自然是后者。就是访问外外设这个操作慢最大程度减少外设访问即多量少次地刷新才最节省时间。 但也不能无脑减少外设访问比如显示器我们习惯按行看它等半天给你突然刷一整屏不合理。 所以虽然最大程度减少外设访问才最节省时间但不同场景也需要不同的刷新策略 无缓冲立即刷新行缓冲按行刷新如显示器全缓冲缓冲区慢刷新如磁盘文件*用户强制刷新如调用fflush*进程退出 说这么多对缓冲区有一定认识了但很重要的一个位置还不知道。 缓冲区的位置 [baconVM-12-5-centos 5-buffer]$ ./test Im printf Im fprintf Im fputs Im write [baconVM-12-5-centos 5-buffer]$ ./test log.txt [baconVM-12-5-centos 5-buffer]$ cat log.txt Im write Im printf Im fprintf Im fputs Im printf Im fprintf Im fputs可以告诉大家这种现象就是和缓冲区有关。但我们现有知识分析不出个所以然能确定的是缓冲区一定不在内核否则write也会打印两次。 所以我们先前讨论的缓冲区都是用户级语言层面的缓冲区(如stdin、stdout、stderr内就有)。 而stdin、stdout、stderr都是FILE类型那也说明语言层面的缓冲区就在FILE内 回想一下我们fflush手动刷新缓冲区是不是要传递FILE指针我们关闭文件文件关闭后会刷新缓冲区是不是要传递FILE指针是的这里也对应上了。 [baconVM-12-5-centos 6-basic_IO]$ vim /usr/include/stdio.h[baconVM-12-5-centos 6-basic_IO]$ vim /usr/include/libio.h*这一堆指针维护的就是语言层面的缓冲区 有了语言层面缓冲区的理解就解释“fork后打印是正常的但是重定向不对劲”这个现象了。 int main() {//Cprintf(Im printf\n);fprintf(stdout, Im fprintf\n);fputs(Im fputs\n, stdout);//system callconst char* msg Im write\n;write(1, msg, strlen(msg));fork();return 0; }[baconVM-12-5-centos 5-buffer]$ ./test Im printf Im fprintf Im fputs Im write [baconVM-12-5-centos 5-buffer]$ ./test log.txt [baconVM-12-5-centos 5-buffer]$ cat log.txt Im write Im printf Im fprintf Im fputs Im printf Im fprintf Im fputs缓冲区在被打开的文件中 没有重定向看到4条信息 向stdout写入刷新策略行缓冲C接口中\n使得缓冲区刷新fork前刷新数据不在缓冲区内fork创建子进程stdout被父子进程共享而后整个程序跑完也就是fork后紧跟着的就是父/子进程退出需要进行缓冲区刷新即修改本来要对缓冲区修改但缓冲区没有数据不会进行修改的操作不会发生写实拷贝平平淡淡打印4条信息 重定向了看到7条信息 向普通文件写入刷新策略全缓冲C接口中\n无法使得缓冲区刷新数据量也达不到全缓冲的条件没有刷新数据还在缓冲区内fork创建子进程log.txt被父子进程共享而后整个程序跑完也就是fork后紧跟着的就是父/子进程退出需要进行缓冲区刷新即修改对共享的被打开文件的缓冲区修改 写时拷贝这也代表父子进程虽然共享被打开的文件但是被打开文件的缓冲区已经又拷贝了一份父子进程各有一份缓冲区退出时分别刷新所以出人意料地C接口在父子进程退出时分别打印了一次总共6次加上系统调用1次最终打印了7条信息 write“不问世事” write并不用FILE而是用fd也就是write不会用到语言层面提供的缓冲区 简单来说 fork创建了子进程父子进程退出分别会刷新自己的缓冲区。而刷新本质是一种拷贝也就是修改 未重定向要写入的文件是显示器文件默认行刷新。 \n使stdout的数据立即刷新父子进程共享的缓冲区在退出时想刷新也并不会发生写实拷贝因为缓冲区内没数据 重定向要写入的文件是普通文件默认全刷新。 \n不能使stdout的数据立即刷新父子进程共享的缓冲区在退出时想刷新会发生写实拷贝因为缓冲区内有数据 只有C语言接口发生写实拷贝 Cstruct file stdout内申请的缓存区空间被父子进程共享且要被写入缓冲区发生写时拷贝。父子进程退出时分别刷新自己的缓冲区最终打印双份信息系统调用通过fd来写入stdout没有C提供的缓冲区 光说不练假把式来自己搞一个行缓冲的缓冲区玩玩。 #自己封装行缓冲的缓冲区 makefile main:main.c myclib.cgcc -o $ $^ -stdc99.PHONY:clean clean:rm -f main#include unistd.h #include stdlib.h #include errno.h #include string.h #include assert.h #include sys/types.h #include sys/stat.h #include fcntl.h#define SIZE 1024 #define SYNC_NOW (1 1) #define SYNC_LINE (1 2) #define SYNC_FULL (1 3)typedef struct myFILE {int flags; //标记位刷新方式int fileno;int cap; //缓冲区容量int size; //缓冲区大小char buffer[SIZE]; }myFILE;myFILE* myfopen(const char* pathName, const char* mode); void myfwrite(const void* ptr, int num, myFILE* pf); void myfclose(myFILE* pf); void myfflush(myFILE* pf);#include myclib.hmyFILE* myfopen(const char* pathName, const char* mode) {//获取打开模式int openFlags 0;int defalutMode 0666;if(strcmp(mode, r) 0)openFlags | O_RDONLY;else if(strcmp(mode, w) 0)openFlags | (O_WRONLY | O_CREAT | O_TRUNC);else if(strcmp(mode, a) 0)openFlags | (O_WRONLY | O_CREAT | O_APPEND);//根据是否需要创建文件来调用不同的openint fd 0;if(openFlags O_RDONLY)fd open(pathName, openFlags);else fd open(pathName, openFlags, defalutMode);if(fd 0){const char* errMsg strerror(errno);write(2, errMsg, strlen(errMsg));return NULL;}//开辟并设置myFILE对象myFILE* pf (myFILE*)malloc(sizeof(myFILE));assert(pf);pf-flags SYNC_LINE; //默认行刷新pf-fileno fd;pf-cap SIZE;pf-size 0;memset(pf-buffer, 0, SIZE);return pf; }void myfwrite(const void* ptr, int num, myFILE* pf) {//1. 写入缓冲区memcpy(pf-buffer pf-size, ptr, num); //不考虑缓冲区溢出pf-size num;//2. 判断是否需要刷新if(pf-flags SYNC_NOW){write(pf-fileno, pf-buffer, pf-size);pf-size 0;}else if(pf-flags SYNC_FULL){if(pf-size pf-cap){write(pf-fileno, pf-buffer, pf-size);pf-size 0;}}else if(pf-flags SYNC_LINE){if(pf-buffer[pf-size - 1] \n){write(pf-fileno, pf-buffer, pf-size);pf-size 0;}} }void myfclose(myFILE* pf) {myfflush(pf);close(pf-fileno); }void myfflush(myFILE* pf) {if(pf-size 0)write(pf-fileno, pf-buffer, pf-size);fsync(pf-fileno); //强制进行外设刷新下文会提到pf-size 0; //惰性删除后来的数据会覆盖 }#include myclib.hint main() {myFILE* pf myfopen(./log.txt, w);assert(pf);int cnt 5;const char* msg testing myclib...\n;while(1){myfwrite(msg, strlen(msg), pf);sleep(1);--cnt;if(cnt 0) break;}myfclose(pf);return 0; }#include myclib.hint main() {myFILE* pf myfopen(./log.txt, w);assert(pf);int cnt 5;const char* msg testing myclib...;while(1){myfwrite(msg, strlen(msg), pf);sleep(1);--cnt;if(cnt 0) break;}myfclose(pf);return 0; }不对不对刚刚说“系统调用通过fd来写入stdout没有C提供的缓冲区”按这个说法write没有缓冲区直接写到磁盘上的文件按理来说不可能因为这样效率也太低了呀是的操作系统层面也有自己的缓冲区。 所以我们就能把缓冲区分两种。 分类 语言层面的缓冲区上面谈的一大堆都是关于语言层面的缓冲区系统层面的内核缓冲区 系统调用fsync NAMEfsync, fdatasync - synchronize a files in-core state with storage deviceSYNOPSIS#include unistd.hint fsync(int fd);可以将文件的内核状态同步到存储设备也就是刷新内核缓冲区。我们自己封装的语言级缓冲区就用到了 void myfflush(myFILE* pf) {if(pf-size 0)write(pf-fileno, pf-buffer, pf-size);fsync(pf-fileno); //强制进行外设刷新下文会提到pf-size 0; }至于内核缓冲区的刷新策略就不是我们能决定的了位置我们也没有过多了解的必要。 学习了缓冲区我们对IO操作的理解就更深刻了。 IO操作的流程 语言接口output操作 信息写入struct file对象中的语言缓冲区语言缓冲区按刷新策略刷新到内核缓冲区内核缓冲区自主决定何时刷新到外设 系统调用output操作 信息写入内核缓冲区内核缓冲区自主决定何时刷新到外设 至此通过对文件的深入理解“基础IO”对我们来说真的就是“基础”了。现在看学C语言时对文件的理解简直太浅显有了现在的理解还会怕IO操作玩不明白嘛 先前我们谈论的都是被打开的文件那没打开的文件呢能肯定的是它们肯定在磁盘上静静地躺着。 那磁盘上的这么老多文件都是怎么管理的呢可以理解为“静态管理”按规则将文件们放到各自的位置。这一整个管理的体系就叫文件系统。 文件系统 是什么 文件系统是一种在存储设备上管理文件的软件系统。 从系统角度来看文件系统是对文件存储设备的空间进行组织和分配具体地说它负责为用户建立文件存入、读出、修改、转储文件控制文件的存取当用户不再使用时撤销文件等。 磁盘的物理结构 磁盘是计算机中唯一的机械结构而且还是外部设备所以它相对其他设备非常慢。 *虽然我们自己很少用很少见磁盘了但因为性价比高不会像ssd那样过多访问可能被击穿等等原因很多企业仍主要用磁盘 磁头就像笔盘面就像纸。运转时盘片高速旋转磁头悬在盘面上不直接和盘面接触。这个场景就像一架波音747在离地1米的距离高速飞行所以一粒灰尘都很可能超过“1米”造成磕碰导致的盘片损毁。 在背面还有硬件电路和伺服系统可以控制磁头和盘片等实现寻址等功能。 那到底磁盘到底是怎样存储数据的怎样表示“0、1”的 可以这样理解盘面上有很多个小小磁铁每个磁铁有N、S极通过磁头充磁、消磁可以对它们的南北极调转从而达到“0、1”的效果。 充磁消磁可以通过加热的方式实现。磁盘内具体是通过电子流动来实现。 有兴趣的朋友可以看看磁盘是怎么运转的 磁盘的存储结构 了解存储结构肯定要具体到某个盘面来讨论。 就是一圈圈同心圆组成的“同心圆”叫磁道分割出的一块块扇形区域就叫扇区。 磁盘寻址的时候基本单位就是扇区一般512bytes。一块一块的这也是为什么有“块设备文件”。 #CHS寻址模式 那在一面上是怎么定位一个扇区的 首先要确定磁道某圈接着通过磁道找到对应的扇区某圈上的某部分。 那是如何定位某个磁道的 磁头的来回摆动就能定位。 又是如何通过磁道找到对应扇区的 盘片的旋转就能配合磁头找到对应扇区。在盘片一次旋转的时间单位时间内通过磁头找到扇区并读取数据。 上面这一点也是磁盘的技术难点提高转速将单位时间缩短不难难的是让磁头读取数据的能力跟上缩短的单位时间。 需要提一下柱面的概念不同盘片上半径相同的所有磁道立体地看就像圆柱故称柱面。其实就是磁道 但是这个概念好像没有存在的必要吧 并不是的因为每面一个的磁头并不能各动各的一个动全都一起动。**那么当磁头定位好、准备寻找扇区的时候所有磁头就能一起出动一找就是n个磁头一起找效率MAX。**所以这个柱面的概念是有必要的。 聊到这我们可以下结论了在磁盘中到底如何定位一个扇区 先移动磁头找到对应磁道柱面再让盘片旋转所有面的磁头一起找目标扇区。 柱面 cylinder磁头 head扇区 sector 这就是CHS寻址模式。 磁盘的逻辑结构 讲磁盘的逻辑结构可以先看看磁带。 磁带的存储介质是一种黑色的塑料材质卷起来像挖掉中心的圆这个圆又是很多同心圆组成。**把它们扯出来拉直其实是一条塑料。**这磁带像不像磁盘的盘片那磁盘的结构也是和这磁带一样“扯出来拉直是一条”是的 磁盘的逻辑结构是线性结构。 虽然磁盘物理上是圆形的但我们可以把它想象成线性的。 上图就很好地表示了对磁盘的抽象逻辑结构变成了一个数组我们将磁盘看作一个sector_arr[]每一个元素是一个扇区。所以要找到一个扇区只要知道这个扇区的下标。这样的下标也叫逻辑块地址(Logic Block Address)。说白了这个数组的下标逻辑块地址标识了一个数据区块。 逻辑是逻辑你终究还得存到物理结构中LBA地址如何对应上CHS寻址模式 比如现有一个磁盘 盘面 4每面的磁道 10每个磁道上的扇区 100扇区大小 512bytes LBA地址sector_arr[123]对应到物理结构上是什么样 这个磁盘大小是4 * 10 * 100 * 512 bytessector_arr的下标范围是4 * 10 * 100。 也可得每一个盘面有1000个扇区。 第几号盘面123 / 1000 0——第0号盘面第几号磁道? 123 / 100 1——第1号磁道第几号扇区123 % 100 23——第23号扇区 sector_arr[123] 第0号盘面中第1号磁道的第23号扇区 如上我们的逻辑结构就能和物理结构搭起来了。 而且我们也能对磁盘来一套管理组合拳抽象、具象、组织、操作。 管理也到位了。 但为什么要通过逻辑结构抽象出LBA地址直接CHS不行吗 便于管理数组肯定比一个三维结构好管理对OS的代码和硬件解耦硬件的底层变了也不影响我OS的代码只要你能转换我给你的LBA地址 磁盘的单次读取 512bytes太小了经过测试性能不高所以 磁盘单次读取一般是4KB。 不会有点浪费吗万一我只访问1bit的数据呢 这其实是一种以空间换时间的做法 对于OS来说一次读取4KB的性能好提高IO的效率 是一种数据预加载提高cpu缓存命中率效率更高 *局部性原理访问某一部分数据它周围的数据很有可能也要被访问。 内存其实也是以为4KB为单位每个单位称为页框。 磁盘中的文件尤其是可执行文件都是被划分成4KB的块这样的块称为页帧。 分区和分组 即便如此4KB对于动辄500GB的磁盘来说还是太小。 解决先分区再分组本质是分治管理好最小子问题就能管理整个问题。 那这也代表对于这么大的磁盘想讨论文件系统只需要讨论这5GB中的构成就够了。 分区 分组 Super Block保存的是整个分区的文件系统的信息 它属于整个分区所以对于每个分组来说不是必须有的每个分组都保存一份可以理解成备份能实现文件系统的恢复 Group Descriptor Table保存分组的属性信息Inode BitMap保存分组内部inode的使用情况对应位置的inode是否被使用Block BitMap保存分组内部Block的使用情况对应位置的Data Block是否被使用Inode Table保存分组内可用已使用未使用的inodeData Blocks保存分组内部所有文件的数据块可动态变化 #inode 是什么 索引节点存储了文件的很多属性在文件系统中非常重要。 之前我们提到过文件 属性 内容在Linux中二者是分开存储的 文件的几乎所有属性保存在自己的大小不变的inode中文件名并不存在inode文件的内容保存在Data Blocks 也就是说某一个文件的属性保存在Inode Table中的某一个Inode内容则保存在Data Blocks中的n个数据块中。 那我们看一个场景在Linux下创建一个文件。 需要先找Inode Table中未使用的inode再找Data Blocks中未使用的数据块。BitMap就是干这事的。 Inode BitMap通过某个比特位的0/1来表示对应位置的Inode是否被使用如 0 0 0 1 0 1 1 0 未使用 未使用 未使用 已使用 未使用 已使用 已使用 未使用Block BitMap同理。 那整个分组有多少个Data Block已经使用了多少还有没少没被使用……这些问题又如何解决 组描述表Group Descriptor Table内就保存了这些数据。 而文件需要区分彼此的inode因此每个inode都有自己的id。 [baconVM-12-5-centos 7-file_system]$ ls -li total 4 922143 drwxrwxr-x 2 bacon bacon 4096 Feb 17 08:40 dir 922141 -rw-rw-r-- 1 bacon bacon 0 Feb 17 08:39 log1.txt 922142 -rw-rw-r-- 1 bacon bacon 0 Feb 17 08:40 log2.txt*ls -i可以显示文件inode的id。 这个inode的编号有什么用查找文件时就是通过inode的编号来查找。 找到了一个文件想获取它的内容可以到Data Blocks去拿但是怎么确定这么多数据块中哪些是和我这个文件有关的呢 inode中有一个字段保存了相关属性 struct inode {int id;mode_t mode;...int blocks[15]; //保存数据块编号... }如此就能确认哪些数据块和当前文件有关。但这样能标识的数据块还是太少(15 * 4KB / 1024B 60KB)。 其实不然谁说数据块里只能保存文件内容数据块内也能保存其他数据块编号多级索引 所以文件和inode、Data Blocks间一定有方式建立关系。 基于文件系统重新理解文件 有了上面的认识对于创建文件也有了新的理解。 创建一个文件 Inode BitMap中某个位置从0变成1根据BitMap中的位置找到Inode Table中对应位置的inode把文件属性填入如果需要写入数据写入Data BlocksBlock BitMap中对应位置的比特位从0变成1inode中填入数据块编号建立inode和Data Blocks间的联系返回inode编号 同理读取一个文件 根据文件inode编号找到Inode Table中属于此文件的inode通过inode中保存的数据块编号找到数据块读取 同理删除一个文件 Inode BitMap中某个位置从1变成0Block BitMap中标识此文件数据块的比特位从1变成0 这是一种惰性删除下次有别的文件来会将原本的属性、内容都覆盖掉。哪些是自己的数据下一个来的文件自己也清楚。 以前可能看见过恢复数据这样的操作跟这里的惰性删除就有关系。 等等你刚刚说“查找文件时就是通过inode的编号来查找”。 但我们Linux用了这么久文件相关的操作一直都用的是文件名这个inode编号…… [baconVM-12-5-centos 7-file_system]$ ls -li total 1 922143 drwxrwxr-x 2 bacon bacon 4096 Feb 17 08:40 dir [baconVM-12-5-centos 7-file_system]$ ls 922143 ls: cannot access 922143: No such file or directory没用啊 任何一个文件一定在某个目录下。目录是文件那么目录也有自己的数据块里边存什么呢 目录的数据块中存的是文件名和inode编号的映射关系。 同一目录下不能有同名文件。那么在某个目录下文件名就是唯一的key值可以通过这个key值索引到inode。 文件名对人友好便于区分和管理但对计算机来说inode编号更简单。 表面上我们是用文件名来进行各种查找和其他操作本质上还是通过inode编号。 我们以前讲权限的时候谈到“要在某个目录下创建文件必须要有这个目录的写入权限”为什么 因为查找文件时就是通过inode的编号来查找在某个目录下通过文件名创建文件需要写入文件名和inode编号的映射关系也就需要对这个目录的数据块写入。 “要读文件必须有这个目录的读取权限”为什么 因为查找文件时就是通过inode的编号来查找读取某个目录下的某个文件需要知道文件名和inode编号的映射关系也就需要读取这个目录中数据块的内容。 所以创建文件和删除文件最后一步还要对目录的数据块进行操作。 以上的这些储备其实可以让我们更好地学习软硬链接。 #软硬链接 先见见猪跑创建一个软链接。 [baconVM-12-5-centos 8-link]$ touch myfile.txt [baconVM-12-5-centos 8-link]$ ln -s myfile.txt soft_file.link [baconVM-12-5-centos 8-link]$ ls -li total 0 922145 -rw-rw-r-- 1 bacon bacon 0 Feb 17 11:53 myfile.txt 922146 lrwxrwxrwx 1 bacon bacon 10 Feb 17 11:53 soft_file.link - myfile.txt再来一个硬链接。 [baconVM-12-5-centos 8-link]$ ln myfile.txt hard_file.link [baconVM-12-5-centos 8-link]$ ll -i total 0 922145 -rw-rw-r-- 2 bacon bacon 0 Feb 17 11:53 hard_file.link 922145 -rw-rw-r-- 2 bacon bacon 0 Feb 17 11:53 myfile.txt 922146 lrwxrwxrwx 1 bacon bacon 10 Feb 17 11:53 soft_file.link - myfile.txt一个inode对应一个文件名但这里hard_file.link和myfile.txt的inode编号居然是一样的而且权限后的数字也变了。 有独立inode的链接文件被称作原文件的软链接没有独立inode的链接文件被称作原文件的硬链接 现在你脑子里肯定有各种疑惑软硬链接到底做了什么权限后的数字又是什么别急我们一步步来。 硬链接做了什么 [baconVM-12-5-centos 8-link]$ ll -i total 0 922145 -rw-rw-r-- 2 bacon bacon 0 Feb 17 11:53 hard_file.link 922145 -rw-rw-r-- 2 bacon bacon 0 Feb 17 11:53 myfile.txt 922146 lrwxrwxrwx 1 bacon bacon 10 Feb 17 11:53 soft_file.link - myfile.txt [baconVM-12-5-centos 8-link]$ echo hello bacon myfile.txt [baconVM-12-5-centos 8-link]$ echo hello bacon myfile.txt [baconVM-12-5-centos 8-link]$ ll -i total 8 922145 -rw-rw-r-- 2 bacon bacon 24 Feb 17 12:03 hard_file.link 922145 -rw-rw-r-- 2 bacon bacon 24 Feb 17 12:03 myfile.txt 922146 lrwxrwxrwx 1 bacon bacon 10 Feb 17 11:53 soft_file.link - myfile.txt [baconVM-12-5-centos 8-link]$ cat myfile.txt hello bacon hello bacon [baconVM-12-5-centos 8-link]$ cat hard_file.link hello bacon hello bacon根据以上的现象我们可以确定硬链接没有新增文件inode没有给硬链接分配其inode。既然如此硬链接文件用的肯定是别人的inode和数据块。 是什么 没错硬链接的本质其实就是在目录的数据块中写入一对新的“inode编号和文件名映射关系”键值对 原文件922145 myfile.txt硬链接文件922145 hard_file.link 那权限后的数字是什么 是inode中的一个计数器其实就是引用计数表示了当前inode的硬链接数。 [baconVM-12-5-centos 8-link]$ rm myfile.txt [baconVM-12-5-centos 8-link]$ ll -i total 4 922145 -rw-rw-r-- 1 bacon bacon 24 Feb 17 12:03 hard_file.link 922146 lrwxrwxrwx 1 bacon bacon 10 Feb 17 11:53 soft_file.link - myfile.txt删除原文件相当于引用计数–还剩下1。我们也能得出一个结论 文件的硬链接数 减至0文件才算真正删除。 但新建一个文件时硬链接数怎么也是1 因为文件本身就具有文件名和inode编号的映射关系也算是硬链接。 诶那这一通操作不就是重命名吗 是的mv重命名的本质就是向目标目录的数据块中写入一对新的映射关系再将原来的删除。 再来看看软链接。 删除之后被soft_file.link链接的这个文件本质上还是存在的但是它这里一直闪烁表示失效 [baconVM-12-5-centos 8-link]$ cat soft_file.link cat: soft_file.link: No such file or directory想操作也说找不到。为什么呢 软链接的本质其实就是在目录的数据块中写入一对新的“文件名路径和文件名映射关系” 不是用inode编号链接建立映射的而是通过文件名路径链接。 因此删除原文件后软链接保存的路径找不到原文件就失效了。按这道理我们重新创建一个文件让软链接保存的路径能找到文件就又可以了 是的只不过这个文件已经是一个全新的文件了。 作用 软链接的作用“快捷方式”。 windows下 Linux下 #在./bin/exe/a/b/c/test下写了一个打印hello link的小程序 [baconVM-12-5-centos 8-link]$ mkdir -p ./bin/exe/a/b/c/test [baconVM-12-5-centos 8-link]$ cd ./bin/exe/a/b/c/test/ [baconVM-12-5-centos test]$ touch test.c [baconVM-12-5-centos test]$ vim test.c [baconVM-12-5-centos test]$ gcc -o test test.c [baconVM-12-5-centos test]$ ll total 16 -rwxrwxr-x 1 bacon bacon 8360 Feb 17 14:56 test -rw-rw-r-- 1 bacon bacon 77 Feb 17 14:56 test.c [baconVM-12-5-centos test]$ cd - /home/bacon/linux/6-basic_IO/8-link #回到/home/bacon/linux/6-basic_IO/8-link想执行一下test却很费劲 [baconVM-12-5-centos 8-link]$ ./bin/exe/a/b/c/test/test hello link #软链接 [baconVM-12-5-centos 8-link]$ ln -s ./bin/exe/a/b/c/test/test test [baconVM-12-5-centos 8-link]$ ll total 4 drwxrwxr-x 3 bacon bacon 4096 Feb 17 14:55 bin lrwxrwxrwx 1 bacon bacon 25 Feb 17 14:59 test - ./bin/exe/a/b/c/test/test [baconVM-12-5-centos 8-link]$ ./test hello link硬链接的作用作目录树状结构中的“父指针”。 [baconVM-12-5-centos 2]$ touch file.txt [baconVM-12-5-centos 2]$ mkdir dir [baconVM-12-5-centos 2]$ ll total 4 drwxrwxr-x 2 bacon bacon 4096 Feb 17 15:11 dir -rw-rw-r-- 1 bacon bacon 0 Feb 17 15:11 file.txtfile.txt的硬链接数是1可以理解但为什么同样是新建dir却是2这就要把.和..拉出来讲讲了。 [baconVM-12-5-centos 2]$ ll -i total 4 922157 drwxrwxr-x 2 bacon bacon 4096 Feb 17 15:11 dir 922156 -rw-rw-r-- 1 bacon bacon 0 Feb 17 15:11 file.txt [baconVM-12-5-centos 2]$ cd dir [baconVM-12-5-centos dir]$ ll -ai total 8 922157 drwxrwxr-x 2 bacon bacon 4096 Feb 17 15:11 . 922155 drwxrwxr-x 3 bacon bacon 4096 Feb 17 15:11 ... 其实就又是一个硬链接。 [baconVM-12-5-centos dir]$ mkdir another_dir [baconVM-12-5-centos dir]$ cd .. [baconVM-12-5-centos 2]$ tree . |-- dir | -- another_dir -- file.txt2 directories, 1 file [baconVM-12-5-centos 2]$ ll -i total 4 922157 drwxrwxr-x 3 bacon bacon 4096 Feb 17 15:29 dir 922156 -rw-rw-r-- 1 bacon bacon 0 Feb 17 15:11 file.txt当我们在dir内创建another_dirdir的硬链接数变成了3 ..也是一层链接 但是并不能给目录建立硬链接。 [baconVM-12-5-centos 2]$ ls dir file.txt [baconVM-12-5-centos 2]$ ln dir hardlink_for_dir ln: ‘dir’: hard link not allowed for directory为什么 为一个目录创建硬链接也就必须对其子目录和子文件创建硬链接也必须对其子目录的子目录和子目录的子文件创建硬链接也必须……这一趟下来可太复杂了也很容易死循环。比如根目录的某个下级目录中有根目录的硬链接当我们访问这个硬链接会走进根目录走进根目录最终又会走到这个下级目录又会走到根目录的硬链接…… 不对啊.和..不正是给目录建立的硬链接吗这不是自相矛盾吗 这俩货 是特殊情况也是OS自己来设置的所以百分百放心不会有问题。 最后我们再解释一下文件的AMC时间。 #文件的AMC时间 Access文件最后访问时间Modify文件属性最后修改时间Change文件内容最后修改时间 [baconVM-12-5-centos 3]$ touch test.txt [baconVM-12-5-centos 3]$ stat test.txt File: ‘test.txt’Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: fd01h/64769d Inode: 922160 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1003/ bacon) Gid: ( 1003/ bacon) #新建后三个时间是统一的 Access: 2023-02-17 15:58:34.797701756 0800 Modify: 2023-02-17 15:58:34.797701756 0800 Change: 2023-02-17 15:58:34.797701756 0800Birth: - #更改了文件属性 [baconVM-12-5-centos 3]$ chmod o-r test.txt [baconVM-12-5-centos 3]$ stat test.txt File: ‘test.txt’Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: fd01h/64769d Inode: 922160 Links: 1 Access: (0660/-rw-rw----) Uid: ( 1003/ bacon) Gid: ( 1003/ bacon) Access: 2023-02-17 15:58:34.797701756 0800 Modify: 2023-02-17 15:58:34.797701756 0800 #Change时间对应更新 Change: 2023-02-17 15:58:59.508849528 0800Birth: -#更改了文件内容 [baconVM-12-5-centos 3]$ echo hello Modify test.txt [baconVM-12-5-centos 3]$ stat test.txt File: ‘test.txt’Size: 13 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 922160 Links: 1 Access: (0660/-rw-rw----) Uid: ( 1003/ bacon) Gid: ( 1003/ bacon) Access: 2023-02-17 15:59:27.807018723 0800 #更改了文件内容所以Modify时间更新 Modify: 2023-02-17 15:59:26.462010682 0800 #但是没再改文件属性Change时间却更新 Change: 2023-02-17 15:59:26.462010682 0800Birth: -为什么因为改了文件内容文件属性也很可能更改了。 [baconVM-12-5-centos 3]$ stat test.txt File: ‘test.txt’Size: 13 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 922160 Links: 1 Access: (0660/-rw-rw----) Uid: ( 1003/ bacon) Gid: ( 1003/ bacon) Access: 2023-02-17 15:59:27.807018723 0800 Modify: 2023-02-17 15:59:26.462010682 0800 Change: 2023-02-17 15:59:26.462010682 0800Birth: - #访问文件 [baconVM-12-5-centos 3]$ cat test.txt hello Modify [baconVM-12-5-centos 3]$ stat test.txt File: ‘test.txt’Size: 13 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 922160 Links: 1 Access: (0660/-rw-rw----) Uid: ( 1003/ bacon) Gid: ( 1003/ bacon) #Access时间没更新 Access: 2023-02-17 15:59:27.807018723 0800 Modify: 2023-02-17 15:59:26.462010682 0800 Change: 2023-02-17 15:59:26.462010682 0800Birth: - #访问文件 [baconVM-12-5-centos 3]$ cat test.txt hello Modify #访问文件 [baconVM-12-5-centos 3]$ cat test.txt hello Modify #访问文件 [baconVM-12-5-centos 3]$ cat test.txt hello Modify [baconVM-12-5-centos 3]$ stat test.txt File: ‘test.txt’Size: 13 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 922160 Links: 1 Access: (0660/-rw-rw----) Uid: ( 1003/ bacon) Gid: ( 1003/ bacon) #Access时间还是没更新 Access: 2023-02-17 15:59:27.807018723 0800 Modify: 2023-02-17 15:59:26.462010682 0800 Change: 2023-02-17 15:59:26.462010682 0800怎么回事如果真的每次访问都更新一下Access时间就意味着每次访问都要对磁盘上的数据更改效率太低。所以Access的更新策略优化成了“一定时间/一定次数后更新”。 #动静态库 概念回顾 以前学习这部分主要的概念就是动静态链接。 静态链接程序编译的时候就将静态库的代码链接拷贝进可执行程序执行时无需链接 动态链接程序执行时才去链接动态库的代码多个程序可以共享同一动态库的代码 链接动态库的可执行文件中只需要保存所用函数的地址表而不是函数对应的机器码。可执行程序执行前会根据地址表把磁盘上动态库中的代码拷贝到内存这个行为称动态链接 具体见这篇文章【Linux03-基本工具之GCC】Linux下的C语言编译器 库的本质 以前张三想用我写的一些接口但我不想把源码暴露给他就给他.o可重定向目标文件和.h头文件他也能正常用。可是一旦源文件多了.o也多了非常不方便。 最后人们决定将多个.o文件组合到一起打包形成一个文件这个文件就是库。 而库的打包方式等又决定了库的类型动/静态库。 了解了库的本质我们可以更深入地学习动静态库和动静态链接。 静态库 我们提到库的本质就是将多个.o文件打包如何打包呢这里要提到一个命令ar。 作用ar是archive的缩写意为归档。这个命令是GNU的归档命令。可以打包.o文件选项 -r replace-c create my_add.h #pragma once #include stdio.hint Add(int x, int y);my_add.c #include my_add.hint Add(int x, int y) {printf(%d %d , x, y);return x y; }my_sub.h #pragma once #include stdio.hint Sub(int x, int y);my_sub.c #include my_sub.hint Sub(int x, int y) {printf(%d - %d , x, y);return x - y; }makefile libmycal.a:my_add.o my_sub.oar -rc $ $^ my_add.o:my_add.cgcc -c my_add.c my_sub.o:my_sub.cgcc -c my_sub.c.PHONY:clean clean:rm -f *.o libmycal.abash [baconVM-12-5-centos 9-lib]$ make gcc -c my_add.c gcc -c my_sub.c ar -rc libmycal.a my_add.o my_sub.o [baconVM-12-5-centos 9-lib]$ file libmycal.a libmycal.a: current ar archive #libmycal.a是归档文件如此就有了一个静态库。那么如何交付呢 交付库 打包压缩动/静态库文件 对应的头文件 先来打包 libmycal.a:my_add.o my_sub.oar -rc $ $^ my_add.o:my_add.cgcc -c my_add.c my_sub.o:my_sub.cgcc -c my_sub.c.PHONY:releasemkdir -p mylib/includemkdir -p mylib/lib cp -f *.h mylib/includecp -f *.a mylib/lib.PHONY:clean clean:rm -f *.o libmycal.a[baconVM-12-5-centos 9-lib]$ make release mkdir -p mylib/include mkdir -p mylib/lib cp -f *.h mylib/include cp -f *.a mylib/lib [baconVM-12-5-centos 9-lib]$ ll total 36 ... drwxrwxr-x 4 bacon bacon 4096 Feb 17 19:33 mylib ... [baconVM-12-5-centos 9-lib]$ tree mylib mylib |-- include | |-- my_add.h | -- my_sub.h -- lib-- libmycal.a2 directories, 3 files再来压缩 [baconVM-12-5-centos 9-lib]$ tar czf mylib.tgz mylib [baconVM-12-5-centos 9-lib]$ ll total 40 ... -rw-rw-r-- 1 bacon bacon 945 Feb 17 19:36 mylib.tgz ...这时只需要把mylib.tgz发给张三就可以了。 解压完后还要安装。安装的本质其实是拷贝 把头文件拷贝到系统的头文件路径中把库文件拷贝到系统的库文件路径中 但我们先不安装直接用。 第一种方案指明路径 [baconVM-12-5-centos libuser]$ gcc -o cal main.c main.c:1:20: fatal error: my_add.h: No such file or directory#include my_add.h^ compilation terminated.使用一下发现找不到头文件。 头文件默认搜索顺序. 系统默认路径 但头文件在./mylib/include并不在.也不在系统默认路径。 解决指定头文件搜索路径——gcc -I ./mylib/include [baconVM-12-5-centos libuser]$ gcc -o cal main.c -I ./mylib/include/ /tmp/ccbnzfvK.o: In function main: main.c:(.text0xf): undefined reference to Add main.c:(.text0x2f): undefined reference to Sub collect2: error: ld returned 1 exit status但链接的时候又找不到Add定义说明是库找不到。 解决指定库文件搜索路径——gcc -L ./mylib/lib [baconVM-12-5-centos libuser]$ gcc -o cal main.c -I ./mylib/include/ -L ./mylib/lib /tmp/ccpjHdMn.o: In function main: main.c:(.text0xf): undefined reference to Add main.c:(.text0x2f): undefined reference to Sub collect2: error: ld returned 1 exit status还不行其实想要链接第三方库非/usr/bin/lib下的库必须要指明库名。 不对啊我们以前用标准库都不用指明库名呢 仅仅使用C标准库gcc能识别用C的标准库g也能识别。但第三方库对于编译器来说无法识别必须指明库名。 解决指定库文件名称——gcc -l libmycal.a [baconVM-12-5-centos libuser]$ gcc -o cal main.c -I ./mylib/include/ -L ./mylib/lib -l libmycal.a /usr/bin/ld: cannot find -llibmycal.a collect2: error: ld returned 1 exit status还不行别忘了库名称是去掉前缀lib和后缀.a/.so。 baconVM-12-5-centos libuser]$ gcc -o cal main.c -I ./mylib/include/ -L ./mylib/lib -l mycal [baconVM-12-5-centos libuser]$ ll total 20 -rwxrwxr-x 1 bacon bacon 8480 Feb 17 20:00 cal -rw-rw-r-- 1 bacon bacon 136 Feb 17 19:45 main.c drwxrwxr-x 4 bacon bacon 4096 Feb 17 19:33 mylib [baconVM-12-5-centos libuser]$ ./cal 10 20 30 10 - 20 -10完事。 [baconVM-12-5-centos libuser]$ ./cal 10 20 30 10 - 20 -10 [baconVM-12-5-centos libuser]$ file cal cal: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]c23e0c892d73684e3d2ae775a9d405551526cc52, not stripped咱们用的是静态库但还是动态链接虽然gcc默认动态链接但这只是建议动态链接。 到底什么链接还是得看具体给了什么库。 只有动态动态链接只有静态静态链接动静都有动态链接 我们用了printf需要C语言的库。尽管系统内可能动静态库都有但gcc默认用动态库所以整体就是动态链接。 第二种方案安装 [baconVM-12-5-centos mylib]$ ll total 8 drwxrwxr-x 2 bacon bacon 4096 Feb 17 19:33 include drwxrwxr-x 2 bacon bacon 4096 Feb 17 19:33 lib #拷贝头文件到系统默认路径 [baconVM-12-5-centos mylib]$ sudo cp ./include/* /usr/include [baconVM-12-5-centos mylib]$ ls /usr/include/my_add.h /usr/include/my_add.h #拷贝库文件到系统默认路径 [baconVM-12-5-centos mylib]$ sudo cp ./lib/* /lib64/ [baconVM-12-5-centos mylib]$ ls /lib64/libmycal.a /lib64/libmycal.a[baconVM-12-5-centos libuser]$ gcc main.c /tmp/ccnB76rp.o: In function main: main.c:(.text0xf): undefined reference to Add main.c:(.text0x2f): undefined reference to Sub collect2: error: ld returned 1 exit status还是不行即便能在默认路径找了但是库很多还是无法确定要链接哪个得指定库名。 [baconVM-12-5-centos libuser]$ gcc main.c -l mycal [baconVM-12-5-centos libuser]$ ll total 20 -rwxrwxr-x 1 bacon bacon 8480 Feb 17 20:20 a.out -rw-rw-r-- 1 bacon bacon 136 Feb 17 19:45 main.c drwxrwxr-x 4 bacon bacon 4096 Feb 17 19:33 mylib [baconVM-12-5-centos libuser]$ ./a.out 10 20 30 10 - 20 -10做完测试还是要把这些东西删掉免得污染。 [baconVM-12-5-centos libuser]$ sudo rm /usr/include/my_* [baconVM-12-5-centos libuser]$ sudo rm /usr/lib64/libmycal.a 这个过程就叫卸载。 动态库 相比静态库仅多了一个选项-fPIC。作用是在编译生成.o文件时产生位置无关码position independent code。 [baconVM-12-5-centos 2]$ cat makefile libmycal.so:my_add.o my_sub.ogcc -shared -o $ $^ my_add.o:my_add.cgcc -c -fPIC my_add.c my_sub.o:my_sub.cgcc -c -fPIC my_sub.c.PHONY:release release:mkdir -p mylib/includemkdir -p mylib/lib cp -f *.h mylib/includecp -f *.so mylib/lib.PHONY:clean clean:rm -rf *.o libmycal.so mylib[baconVM-12-5-centos 2]$ make gcc -c -fPIC my_add.c gcc -c -fPIC my_sub.c gcc -shared -o libmycal.so my_add.o my_sub.o [baconVM-12-5-centos 2]$ clear [baconVM-12-5-centos 2]$ make release mkdir -p mylib/include mkdir -p mylib/lib cp -f *.h mylib/include cp -f *.so mylib/lib [baconVM-12-5-centos 2]$ ll total 40 ... drwxrwxr-x 4 bacon bacon 4096 Feb 17 20:48 mylib ... [baconVM-12-5-centos 2]$ tree mylib/ mylib/ |-- include | |-- my_add.h | -- my_sub.h -- lib-- libmycal.so2 directories, 3 files [baconVM-12-5-centos 2]$ cp -rf mylib ../../libuser[baconVM-12-5-centos libuser]$ pwd /home/bacon/linux/6-basic_IO/libuser [baconVM-12-5-centos libuser]$ gcc -o cal main.c -Imylib/include -Lmylib/lib -lmycal [baconVM-12-5-centos libuser]$ ll total 20 -rwxrwxr-x 1 bacon bacon 8432 Feb 17 20:57 cal -rw-rw-r-- 1 bacon bacon 136 Feb 17 20:56 main.c drwxrwxr-x 4 bacon bacon 4096 Feb 17 20:56 mylib [baconVM-12-5-centos libuser]$ ./cal ./cal: error while loading shared libraries: libmycal.so: cannot open shared object file: No such file or directory发现加载动态库的时候有问题。 [baconVM-12-5-centos libuser]$ ldd callinux-vdso.so.1 (0x00007ffffe9aa000)libmycal.so not foundlibc.so.6 /lib64/libc.so.6 (0x00007fce6e43e000)/lib64/ld-linux-x86-64.so.2 (0x00007fce6e80c000)库文件的路径和名称我都交代了啊怎么还找不到 我们是给gcc交代的但是当程序编译完就没gcc的事儿了运行前动态链接还得找OS。OS和shell也得知道库在哪里。 我们的库不在系统路径下所以运行前OS找不到这个库。 解决1把我们的路径追加到环境变量LD_LIBRARY_PATHshell运行时会自动到这里找 [baconVM-12-5-centos libuser]$ export LD_LIBRARY_PATH$LD_LIBRARY_PATH:/home/bacon/linux/6-basic_IO/libuser/mylib/lib [baconVM-12-5-centos libuser]$ echo $LD_LIBRARY_PATH :/home/bacon/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/bacon/linux/6-basic_IO/libuser/mylib/lib[baconVM-12-5-centos libuser]$ gcc -o cal main.c -Imylib/include -Lmylib/lib -lmycal [baconVM-12-5-centos libuser]$ ll total 20 -rwxrwxr-x 1 bacon bacon 8432 Feb 17 21:08 cal -rw-rw-r-- 1 bacon bacon 136 Feb 17 20:56 main.c drwxrwxr-x 4 bacon bacon 4096 Feb 17 20:56 mylib [baconVM-12-5-centos libuser]$ ./cal 10 20 30 10 - 20 -10完事。 但这样导的环境变量是临时的用来测试一下还行长久用还得每次导不方便。 解决2拷贝到系统路径下 不演示了。 解决3在/etc/ld.so.conf.d添加配置文件 *shell也会在这里的配置文件中找 [baconVM-12-5-centos libuser]$ cd /etc/ld.so.conf.d [baconVM-12-5-centos ld.so.conf.d]$ sudo touch test.conf [baconVM-12-5-centos ld.so.conf.d]$ ll total 16 -rw-r--r-- 1 root root 26 Feb 24 2022 bind-export-x86_64.conf -rw-r--r-- 1 root root 19 Aug 9 2019 dyninst-x86_64.conf -r--r--r-- 1 root root 63 Jun 28 2022 kernel-3.10.0-1160.71.1.el7.x86_64.conf -rw-r--r-- 1 root root 17 Oct 2 2020 mariadb-x86_64.conf -rw-r--r-- 1 root root 0 Feb 17 21:14 test.conf在test.conf中添加我们的库的路径。 [baconVM-12-5-centos ld.so.conf.d]$ cat test.conf /home/bacon/linux/6-basic_IO/libuser/mylib/lib[baconVM-12-5-centos libuser]$ ./cal ./cal: error while loading shared libraries: libmycal.so: cannot open shared object file: No such file or directory [baconVM-12-5-centos libuser]$ ldd cal linux-vdso.so.1 (0x00007ffedcda4000)libmycal.so not foundlibc.so.6 /lib64/libc.so.6 (0x00007fc53ebba000)/lib64/ld-linux-x86-64.so.2 (0x00007fc53ef88000)还是找不到……因为配置文件还没更新。 使用ldconfig加载配置文件命令即可。 [baconVM-12-5-centos ld.so.conf.d]$ sudo ldconfig [baconVM-12-5-centos etc]$ cd /home/bacon/linux/6-basic_IO/libuser [baconVM-12-5-centos libuser]$ ldd callinux-vdso.so.1 (0x00007ffe7855f000)libmycal.so /home/bacon/linux/6-basic_IO/libuser/mylib/lib/libmycal.so (0x00007fa5450db000)libc.so.6 /lib64/libc.so.6 (0x00007fa544d0d000)/lib64/ld-linux-x86-64.so.2 (0x00007fa5452dd000) [baconVM-12-5-centos libuser]$ ./cal 10 20 30 10 - 20 -10完事。 解决4软链接 #把配置文件去掉 [baconVM-12-5-centos ld.so.conf.d]$ sudo rm -f test.conf [baconVM-12-5-centos ld.so.conf.d]$ ll total 16 -rw-r--r-- 1 root root 26 Feb 24 2022 bind-export-x86_64.conf -rw-r--r-- 1 root root 19 Aug 9 2019 dyninst-x86_64.conf -r--r--r-- 1 root root 63 Jun 28 2022 kernel-3.10.0-1160.71.1.el7.x86_64.conf -rw-r--r-- 1 root root 17 Oct 2 2020 mariadb-x86_64.conf [baconVM-12-5-centos ld.so.conf.d]$ sudo ldconfig [baconVM-12-5-centos ld.so.conf.d]$ cd - /home/bacon/linux/6-basic_IO/libuser #无法找到动态库 [baconVM-12-5-centos libuser]$ ./cal ./cal: error while loading shared libraries: libmycal.so: cannot open shared object file: No such file or directory [baconVM-12-5-centos libuser]$ ldd callinux-vdso.so.1 (0x00007ffd4b1e1000)libmycal.so not foundlibc.so.6 /lib64/libc.so.6 (0x00007fd56a245000)/lib64/ld-linux-x86-64.so.2 (0x00007fd56a613000)#软链接 [baconVM-12-5-centos libuser]$ ln -s ~/linux/6-basic_IO/libuser/mylib/lib/libmycal.so libmycal.so [baconVM-12-5-centos libuser]$ ll total 20 -rwxrwxr-x 1 bacon bacon 8432 Feb 17 21:08 cal lrwxrwxrwx 1 bacon bacon 58 Feb 17 21:29 libmycal.so - /home/bacon/linux/6-basic_IO/libuser/mylib/lib/libmycal.so -rw-rw-r-- 1 bacon bacon 136 Feb 17 20:56 main.c drwxrwxr-x 4 bacon bacon 4096 Feb 17 20:56 mylib [baconVM-12-5-centos libuser]$ ./cal 10 20 30 10 - 20 -10运行时找在当前路径下找libmycal.so会软链接至/home/bacon/linux/6-basic_IO/libuser/mylib/lib/libmycal.so。 完事。 #安装第三方库 来一个黑框框图形化界面的库。 先下载 [baconVM-12-5-centos ~]$ sudo yum install ncurses-devel检查一下是有的 [baconVM-12-5-centos test]$ ls /usr/include/ncurses.h -l lrwxrwxrwx 1 root root 8 Feb 18 09:42 /usr/include/ncurses.h - curses.h写个hello world #include string.h #include ncurses.hint main(){initscr();raw();noecho();curs_set(0);const char* c Hello, World!;mvprintw(LINES/2,(COLS-strlen(c))/2,c);refresh();getch();endwin();return 0; }编译 [baconVM-12-5-centos test]$ gcc -o test test.c -lncurses效果 理解动静态编译 静态编译 使用静态库、静态链接的编译行为称静态编译。 静态库的链接 静态库会在编译时拷贝链接到可执行程序。 静态库的“加载” 其实静态库不需要加载它就嵌在可执行程序中。 类似绝对编址编译时静态库的代码具体在哪里已经根据虚拟地址空间的内存划分规则被写死了。 运行时静态库的访问 那这样的可执行程序运行时静态库的代码在哪里呢 只能是虚拟地址空间中的代码段区域。往后进程访问静态库中的代码如printf都是通过绝对地址找到printf的入口。 usec.c #include stdio.hint main() {printf(hello wolrd!\n);return 0; }[baconVM-12-5-centos test]$ gcc -static -o s_usec usec.c [baconVM-12-5-centos test]$ lld s_usec -bash: lld: command not found [baconVM-12-5-centos test]$ ldd s_usec not a dynamic executable #静态链接不保存相对地址静态编译 可执行程序中存静态库被使用的部分和其绝对地址程序要访问静态库部分就通过绝对地址访问 动态编译 使用动态库动态链接的编译行为称动态编译。 动态库的链接 我们前面提到的-fPIC产生位置无关码其实就是一种相对编址。 可执行程序中会保存位置无关码。这些相对地址是动态库中被使用部分在动态库中的相对地址。 动态库的加载 程序载入内存时通过相对地址把动态库的一部分载入代码段 动态库的访问 程序即将访问动态库部分时通过相对地址把动态库的一部分载入代码段 [baconVM-12-5-centos test]$ gcc -o usec usec.c #动态链接可执行内保存相对地址 [baconVM-12-5-centos test]$ ldd useclinux-vdso.so.1 (0x00007ffef80f4000)libc.so.6 /lib64/libc.so.6 (0x00007f6026e16000) #printf的相对地址/lib64/ld-linux-x86-64.so.2 (0x00007f60271e4000)动态编译 可执行程序中存动态库被使用部分在动态库中的相对地址程序即将访问动态库部分时通过相对地址把动态库的一部分载入内存把动态库的一部分通过页表映射到共享区此时库的起始位置确定。访问动态库部分 共享区中库的起始位置 可执行中存的相对位置 为什么编译源文件要加-fPIC这样编译出来的.o中函数地址都是相对地址位置无关码了。 为什么打包成库时要加-shared这样打包出来的库文件就是动态库格式。 可算结束了这一章节可真不简单。 表层需要讲的其实就是文件描述符、软硬链接、动静态库但底层牵扯的知识又很多。只有把背后的知识了解清楚我们才能真正理解表层内容。 很高兴你能看到这本期的分享告一段落了。 这里是培根的blog期待与你共同进步 下期见~
文章转载自:
http://www.morning.sgmgz.cn.gov.cn.sgmgz.cn
http://www.morning.tgwfn.cn.gov.cn.tgwfn.cn
http://www.morning.kqxwm.cn.gov.cn.kqxwm.cn
http://www.morning.ysmw.cn.gov.cn.ysmw.cn
http://www.morning.mzydm.cn.gov.cn.mzydm.cn
http://www.morning.cpqwb.cn.gov.cn.cpqwb.cn
http://www.morning.ckhyj.cn.gov.cn.ckhyj.cn
http://www.morning.mcjrf.cn.gov.cn.mcjrf.cn
http://www.morning.ypwlb.cn.gov.cn.ypwlb.cn
http://www.morning.kdldx.cn.gov.cn.kdldx.cn
http://www.morning.zwtp.cn.gov.cn.zwtp.cn
http://www.morning.sbpt.cn.gov.cn.sbpt.cn
http://www.morning.znpyw.cn.gov.cn.znpyw.cn
http://www.morning.rfrx.cn.gov.cn.rfrx.cn
http://www.morning.zrpys.cn.gov.cn.zrpys.cn
http://www.morning.rfljb.cn.gov.cn.rfljb.cn
http://www.morning.lbjdx.cn.gov.cn.lbjdx.cn
http://www.morning.qhjkz.cn.gov.cn.qhjkz.cn
http://www.morning.mqbsm.cn.gov.cn.mqbsm.cn
http://www.morning.lskrg.cn.gov.cn.lskrg.cn
http://www.morning.yskhj.cn.gov.cn.yskhj.cn
http://www.morning.xjqkh.cn.gov.cn.xjqkh.cn
http://www.morning.fynkt.cn.gov.cn.fynkt.cn
http://www.morning.zgdnd.cn.gov.cn.zgdnd.cn
http://www.morning.fkflc.cn.gov.cn.fkflc.cn
http://www.morning.hqxyt.cn.gov.cn.hqxyt.cn
http://www.morning.dpgdj.cn.gov.cn.dpgdj.cn
http://www.morning.wnhml.cn.gov.cn.wnhml.cn
http://www.morning.jzccn.cn.gov.cn.jzccn.cn
http://www.morning.stflb.cn.gov.cn.stflb.cn
http://www.morning.zryf.cn.gov.cn.zryf.cn
http://www.morning.xhwty.cn.gov.cn.xhwty.cn
http://www.morning.ktmbp.cn.gov.cn.ktmbp.cn
http://www.morning.rczrq.cn.gov.cn.rczrq.cn
http://www.morning.lflsq.cn.gov.cn.lflsq.cn
http://www.morning.xjnw.cn.gov.cn.xjnw.cn
http://www.morning.hlwzd.cn.gov.cn.hlwzd.cn
http://www.morning.plznfnh.cn.gov.cn.plznfnh.cn
http://www.morning.gagapp.cn.gov.cn.gagapp.cn
http://www.morning.mnmrx.cn.gov.cn.mnmrx.cn
http://www.morning.banzou2034.cn.gov.cn.banzou2034.cn
http://www.morning.pndhh.cn.gov.cn.pndhh.cn
http://www.morning.xqjh.cn.gov.cn.xqjh.cn
http://www.morning.tfcwj.cn.gov.cn.tfcwj.cn
http://www.morning.junmap.com.gov.cn.junmap.com
http://www.morning.bpmtg.cn.gov.cn.bpmtg.cn
http://www.morning.rfhwc.cn.gov.cn.rfhwc.cn
http://www.morning.pfcrq.cn.gov.cn.pfcrq.cn
http://www.morning.zkbxx.cn.gov.cn.zkbxx.cn
http://www.morning.jfbgn.cn.gov.cn.jfbgn.cn
http://www.morning.pqkgb.cn.gov.cn.pqkgb.cn
http://www.morning.tmnyj.cn.gov.cn.tmnyj.cn
http://www.morning.tkqzr.cn.gov.cn.tkqzr.cn
http://www.morning.wfkbk.cn.gov.cn.wfkbk.cn
http://www.morning.lcbt.cn.gov.cn.lcbt.cn
http://www.morning.btqqh.cn.gov.cn.btqqh.cn
http://www.morning.rqxmz.cn.gov.cn.rqxmz.cn
http://www.morning.qhmql.cn.gov.cn.qhmql.cn
http://www.morning.dwwlg.cn.gov.cn.dwwlg.cn
http://www.morning.nptls.cn.gov.cn.nptls.cn
http://www.morning.clbgy.cn.gov.cn.clbgy.cn
http://www.morning.llcgz.cn.gov.cn.llcgz.cn
http://www.morning.wgcng.cn.gov.cn.wgcng.cn
http://www.morning.pzjrm.cn.gov.cn.pzjrm.cn
http://www.morning.wnhgb.cn.gov.cn.wnhgb.cn
http://www.morning.ypcd.cn.gov.cn.ypcd.cn
http://www.morning.xysdy.cn.gov.cn.xysdy.cn
http://www.morning.mbfkt.cn.gov.cn.mbfkt.cn
http://www.morning.prprj.cn.gov.cn.prprj.cn
http://www.morning.ypdmr.cn.gov.cn.ypdmr.cn
http://www.morning.rmryl.cn.gov.cn.rmryl.cn
http://www.morning.qwlml.cn.gov.cn.qwlml.cn
http://www.morning.rythy.cn.gov.cn.rythy.cn
http://www.morning.fzqfb.cn.gov.cn.fzqfb.cn
http://www.morning.pdkht.cn.gov.cn.pdkht.cn
http://www.morning.sgrwd.cn.gov.cn.sgrwd.cn
http://www.morning.wjrq.cn.gov.cn.wjrq.cn
http://www.morning.ktxd.cn.gov.cn.ktxd.cn
http://www.morning.sfnr.cn.gov.cn.sfnr.cn
http://www.morning.dwmmf.cn.gov.cn.dwmmf.cn
http://www.tj-hxxt.cn/news/275242.html

相关文章:

  • 经营范围网站建设楼盘网站建设方案
  • 达州市网站建设上海关键词排名搜索
  • 狠狠做狠狠干免费网站宝塔建站网址
  • 欧美一级A做爰片成电影网站网站开发有很多种吗
  • 迁安做网站中的cms开发网站建设广告图片
  • 中国建设工程标准化协会网站青岛互联网公司排名
  • 律所网站方案简述网站制作的过程
  • 您的网站对百度设置了ua封禁z怎么解决网站内容的特点
  • logo免费下载网站9377传奇手游盒子
  • 深圳哪家公司需要网站建设的国内无代码开发平台
  • 怎么做好网站营销推广货代网站制作
  • 秀洲住房与建设局网站门户网站seo前期
  • 旅店网站建设规划书建设网店网站
  • 行业网站 源码南京网站制作公司有哪些
  • 重庆seo整站优化方案范文wordpress使用腾讯云存储
  • 手机网站的开发58同城旧房翻新
  • html5可不可以建设手机网站网站开发与没计是做什么
  • 做彩票网站收费标准游戏排行榜页游
  • 网站域名选择的原则网站制作哪个软件
  • 网站关键词怎么添加比分网站建设
  • 在婚纱店做网站优化wordpress 弹出窗口
  • 湖州网站开发制作商城网站
  • 做python一个网站2022年明星百度指数排行
  • 商城网站建设如何seo优化快排
  • 网站小图标素材下载怀柔网站制作
  • 天津建设工程信息网网站首页时彩网站开发
  • 三明网站建设tudoulywordpress 觉唯主题
  • 南宁学做网站wordpress中介模板
  • 交互式网站设计马鞍山钢铁建设集团有限公司网站
  • 外贸网站seo优化方案响水做网站哪家公司好