库函数:就是对系统调用接口的一层封装 fopen; fread; fwrite; fseek; fclose FILE *fopen(char *filename, char mode) mode: 文件的打开方式- 只读, 只写, 读写, 追加写 “r” -------------只读–若文件不存在就会打开失败 “r+”------------读写 “w”------------只写–若文件不存在就会创建新文件,若文件已经存在, 清空文件原有内容打开文件 “w+”----------读写 “a”------------追加写–在每次写入文件数据的时候总是写入文件末尾—文件不存在就会创建新文件 “a+”----------追加读写 “b”------------fopen打开文件默认是文本操作, 如果使用b则表示进行二进制操作 返回值: 返回一个FILE的文件流指针作为文件的操作句柄,失败返回NULL
size_t (无符号的32位整形数字) fread(char* buf, size_t block_size, size_t block_count, FILE* fp);(缓冲区, 块大小, 块个数, 文件流指针) size_t fwrite(char* data, size_t block_size, size_t block_count,FILE* fp);(数据首地址, 块大小, 块个数, 文件流指针);
fread 如果读到了文件末尾会返回0; fread/fwrite 比较推荐块大小为1,块个数是想要操作的数据长度
int fseek(FILE* fp, long offset, int whence);(将文件的读写指针从whence位置偏移offset个字节)—>跳转文件读写位置 fseek:文件没有数据也可以跳转读写位置,对文件数据进行字符串操作的时候要注意文件数据中’\0’这种数据
int fclose(FILE* fp);关闭文件流指针,释放资源
open/read/write/lseek/close *int open(char filename, int flag, mode_t mode); filename:要打开的文件名称 flag:选项参数–文件的打开方式 必选项/可选项 必选项:O_RDONLY 只读 O_WRONLY 只写 O_RDWR-读写(只能选择一个) 可选项:O_CREAT-文件存在则打开,不存在则创建;O_EXCL与O_CREAT同时使用,文件存在则报错;O_TRUNC-打开文件的同时清空原有内容;O_APPEND-追加写,总是将数据写入到文件的末尾 mode:权限—如果使用了O_CREAT有可能创建新文件,那就一定要指定文件的权限 返回值:一个非负整数—文件描述符—文件的操作句柄;失败返回-1
*(int类型)ssize_t write(int fd, char buf, size_t count); fd:open返回的文件描述符—文件的操作句柄 buf:要写入的文件数据的空间首地址 count:要写入的数据大小 返回值:返回实际写入文件的数据字节长度,失败返回-1
文件描述符:是一个非负整数–操作文件的句柄 文件描述符其实就是内核中一个进程打开的文件描述信息表的下标–通过这个下标可以在内核中找到相应的文件描述信息,通过这个描述信息可以实现文件的操作 注意:如果打开一个文件,如果不操作了一定要关闭,释放资源;(文件描述符实际是有限的)若不关闭文件,文件描述符用光,则在进程中就打不开新文件了 一个程序运行起来,进程中会默认打开三个文件:标准输入-0/标准输出-1/标准错误-2 文件描述符有个分配规则:最小未使用;
将文数据不在写入原本的文件,而是写入新的指定的文件中—实现方式就是替换这个描述符对应的文件描述信息(实际上是描述符重定向,改变描述符所指向的文件,就改变了数据的流向) int dup2(int oldfd, int new fd) — 描述符重定向函数 ------ 让newfd这个描述符也指向oldfd所指向的文件,这时候oldfd和newfd都能够操作oldfd所指向的文件
之前博客中有写过一个minishell,学了dup2函数之后,做一个在minishell中实现> , >>的标准输出重定向: ls -l >a.txt 中的>是清空重定向open(O_CREAT|O_TRUNC) 。>> 是追加重定向open(O_CREAT|O_APPEND) a.txt 的fd = open(a.txt); dup2(fd, 1)子进程在运行指令的时候ls本身要将数据写入标准输入 代码如下:
#include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <ctype.h> 6 #include <sys/wait.h> int main(int argc, char *argv[]) { while(1){ printf("[shen]@localhost$ ");//增加一个shell提示 fflush(stdout);//刷新标准输出缓冲区 //1.等待标准输入 char tmp[1024] = {0}; gets(tmp); //解析重定向 //ls -l > a.txt char *ptr =tmp; int redirect_flag = 0; char *redirect_file = NULL; while(*ptr != '\0'){ if(*ptr == '>'){ redirect_flag = 1;//这是清空重定向 *ptr = '\0';//将>替换成结尾标志,则命令的解析到此位置就完毕了 ptr++; if(*ptr == '>'){//有第二个>则是追加重定向 redirect_flag = 2; } while(*ptr == ' ' && *ptr != '\0')ptr++;//将a.txt之前的空格走完 redirect_file = ptr;//redirect_file这个指针指向了a.txt中a的位置 while(*ptr != ' ')ptr++;//将a.txt字符走完 *ptr = '\0'; } ptr++; } //解析字符串 char *argv[32] = {NULL}; int argc = 0; ptr = tmp; while(*ptr != '\0'){ if(!isspace(*ptr)){ argv[argc]= ptr; argc++; while(!isspace(*ptr) && *ptr != '\0')ptr++; *ptr = '\0'; } ptr++; } argv[argc]= NULL; //运行argv[0]这个程序 //1.创建子进程 pid_t pid = fork(); if(pid < 0){ perror("fork error"); return -1; } else if(pid == 0){ if(redirect_flag == 1){//这是清空重定向 int fd = open(redirect_file, O_WRONLY|O_CREAT|O_TRUNC, 0664); dup2(fd, 1);//将标准输入重定向到redirect_file;原本打印的数据就会被写入文件 } else if(redirect_flag == 2){//这是追加重定向 int fd = open(redirect_file, O_WRONLY|O_CREAT|O_APPEND, 0664); dup2(fd, 1);//将标准输入重定向到redirect_file;原本打印的数据就会被写入文件 } else if(redirect_flag == 2){//这是追加重定向 } //2.程序替换 execvp(argv[0], argv); exit(0);//替换失败则直接退出 } wait(NULL);//等待子进程退出 } return 0; }文件描述符:是一个非负整数 —系统调用的IO接口 文件流指针:FILE结构体 – typedef struct _IO_FILE FILE --库函数IO接口的操作句柄,它是一个结构体,结构体中有很多成员变量, 其中就有一个叫_fileno—这就是文件描述符 例如:fwrite( FILE * fp, helloworld)-----> write(fd, helloworld) 库函数是对系统调用接口的一层封装 通过文件流指针进行最终文件操作的时候,依然还要能够找到文件对应的文件描述符才可以 注意:缓冲区实际上是文件流指针结构体中的缓冲区(用户态缓冲区); 向文件写入数据,并不会直接写入文件,而是先写入缓冲区中,刷新缓冲区的时候才会写入文件;而系统调用接口是直接将数据写入文件的,系统调用接口是没有缓冲区的,只有库函数才存在缓冲区 exit退出会刷新缓冲区 / _exit退出时不会刷新缓冲区 (加入缓冲区是为了减少IO次数) 一个进程如何从用户态切换到内核态运行? 系统调用接口, 异常, 中断5
文件系统就是磁盘上管理文件的系统 存储一个文件需要找到空闲的磁盘块存储文件数据,以及需要找到一个未被使用的inode节点存储自己的元信息 针对每一个磁盘块做一个整体的位图,这样就可以快速找到空闲的磁盘块
文件的存储流程:通过超级块找到inode位图/数据块位图,通过数据块位图快速的找到空闲的磁盘块存储文件数据,通过inode位图快速找到空闲inode节点,存储文件的元信息 ----文件数据存储完毕;每个文件都有一个唯一的inode节点(ls -i ) 位图:一连串的二进制比特位,用来做一种标记(文件的权限就是使用位图存储) 1.在超级块中获取文件系统信息(各个区域起始位置),找到两个位图区域 2.根据位图信息,找到空闲的inode节点,以及空闲的数据块 3.存储数据到数据块中,存储文件的元信息到inode节点中 4.将文件名与inode节点号,作为对应信息(目录项)保存在所在目录的文件中
linux下文件的数据和文件的名称是分离的,文件数据存储完毕之后,还需要在这个文件的所在目录下(目录文件中),记录这个文件名称以及inode节点号(目录项)
获取文件数据的流程:通过文件名打开一个文件, 在所在目录文件中通过文件名找到这个文件的inode节点号;在磁盘超级块中找到inode节点区域,根据inode节点号,找到inode节点,得到数据存储的磁盘块号,取出数据
软链接文件/硬链接文件:给一个源文件创建一个软链接文件/硬链接文件,就可以通过被创建出来的软链接/硬链接文件来操作源文件 创建一个硬链接文件: ln test.txt test.hard 创建一个软链接文件: ln -s test.txt test.soft 软链接文件和硬链接文件的区别: 硬链接文件:本质上和源文件没有什么不同,都是一个文件的名称,与源文件共用一个inode节点, 通过自己的inode节点访问源文件的数据 软链接文件:本质上是一个独立的文件,有自己的inode节点,文件数据中保存源文件的路径,通过这个路径访问源文件的数据 区别:删除源文件后, 软链接文件失效, 硬链接文件只是链接数 - 1;链接数:一个inode节点对应有几个目录项(删除一个文件,文件并不会立即被删除,而是直接删除了目录项信息,inode中的链接数 - 1,之后链接数为0时,才会真正删除) 1.删除源文件,则软链接文件会失效,但是硬链接文件不受影响 2.软链接文件可以跨分区建立,但是硬链接文件不可以 3.软链接文件可以对目录创建,但是硬链接文件不可以
生成:将当前的各个.c文件生成各自的.o文件;将所有的.o文件以及使用到的库文件进行打包生成我们自己的库文件 将当前的各个.c文件生成各自的.o文件: gcc -c ***.c -o ***.o 动态库:gcc -fPIC -c ***.c -o ***.o gcc --shared ***.o .o… -o lib.so 静态库:gcc -c ***.c -o .o ar -cr lib.a ***.o ***.o… 使用:生成可执行程序时的链接使用;运行可执行程序时的加载使用(只针对动态库) 生成可执行程序时的链接使用: 生成可执行程序使用的是gcc编译器,在生成可执行程序的时候,使用 -l 选项指定要链接的库文件名称 例如库文件名称libmytest.so/libmychild.a gcc main.c -o main -lmychild(去掉前缀和后缀) gcc编译器在生成可执行程序时,假如链接了库文件,就会默认去指定的一些路径下找这些库文件 1.将库文件放到默认指定的路径下 /lib64 2.环境变量LIBRARY_PATH保存默认查找路径,将库文件所在路径添加到环境变量中,export LIBRARY_PATH=${LIBRARY_PATH}:./LIB 3.使用gcc的编译选项-L指定库文件所在路径:gcc main.c -o main -lchild -L./
运行可执行程序时的加载使用 程序运行时,会自动根据程序依赖的动态库信息,去将指定的动态库加载到内存 加载时,就会默认的去指定的路径下找这个库文件 1.将库文件放到指定的路径下 /lib64; 2.环境变量LD_LIBRARY_PATH保存加载路径;