Linux基础IO
-
标准库接口
fopen fclose fwrite fread fseek
-
fopen
- r 只读方式打开文件
- r+ 读写方式打开文件
- w 只写方式打开文件, 文件不存在则创建, 存在则清空内容
- w+ 读写方式打开文件, 文件不存在则创建,每次写入数据都是写入文件末尾
- a 写追加方式打开文件, 文件不存在则创建,每次写入数据都是写入末尾
- a+ 可读, 写追加方式打开文件, 文件不存在则创建, 读数据的初始位置在文件起始,写数据一直追加在文件末尾
-
fgets 获取一行数据
-
标准输出
fprintf sprintf snprintf printf
-
标准输入
sscanf
-
stdin & stdout & stderr
c默认会打开三个输入输出流, 分别是stdin, stdout, stderr
三个流的类型都是FILE* , fopen 返回值类型, 文件指针
-
-
系统库接口
-
open
#includ e <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode) int creat(const char *pathname, mode_t mode); // 返回值是文件描述符
-
read
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); // 返回值 // 成功时返回读取到的字节数(为零表示读到文件描述符),此返回值受文件剩余字节数限制.当返回值小于指定的字节数时并不意味着错误;这可能是因为当前可读取的字节数小于指定的 字节数(比如已经接近文件结尾,或者正在从管道或者终端读取数 据,或者 read()被信号中断).发生错误时返回-1,并置 errno 为相应值.在这种情况下无法得知文件偏移位置是否有变化.
-
flag:
O_RDONLY // 只读打开 O_WRONLY // 只写打开 O_RDWR // 读,写打开 O_CREAT // 若文件不存在,则创建它, 需要使用mode选项,来指明新文件的访问权限 O_TRUNC // 若文件存在,则长度被截为0,属性不变 O_APPEND // 追加写 O_EXCL // 未用;对UNIX系统兼容
-
文件描述符
每个进程在PCB(Process Control Block)即进程控制块中都保存着一分文件描述符表,文件描述符就是这个表的索引,文件描述符表中每个表项都有一个指向已打开文件的指针。现在我们明确一下:已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。
-
fd
fd:为打开文件的文件描述符,而每个进程都有一张文件描述符表,fd文件描述符就是这张表的索引,同样这张表中有一表项,该表项又是指向前面提到打开文件的file结构体,file结构体才是内核中用来描述文件属性的结构体
-
序执行时,就已经有三个标准文件流打开了,它们分别是(标准输入)stdin,(标准输出)stdout,(标准错误输出)stderr,和流式文件相对应的是,也有三个文件描述符被预先占用
0((标准输入)stdin),1((标准输出)stdout),2((标准错误输)stderr))
-
-
文件描述符与文件流指针的区别
fd只是一个整数,在open时产生,起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针file。
-
open与fopen的对比
open:文件描述符的操作(如:open)返回的是一个文件描述符(int fd),内核会在每个进程空间中维护一个文件描述符表,所有打开的文件都将通过,此表中的文件描述符来引用。
fopen:流(如:fopen)返回的是一个文件指针(即指向FILE结构体的指针),FILE结构是包含有文件描述符的,fopen可以看做是open(fd直接操作的系统调用)的封装,它的优点是带有I/O缓存。
C语言的文件指针与文件描述符的相互转换可通过fdopen和fileno两个函数实现。它们都包含在头文件stdio.h中。FILE *fdopen(int filedes,const char *opentype); //filedes是一个打开的文件描述符, // opentype是表示打开方式的字符串,和fopen函数具有相同的取值,比如”w”或”w+”等。但是你必须保证该字符串的描述和文件实际的打开方式是匹配的。函数fopen()就是返回打开文件的指针;如果操作失败,返回空指针null。 // 把文件流指针转换成文件描述符用fileno函数 int fileno(FILE *stream); // 返回值是和stream文件流对应的文件描述符。如果失败,返回-1;
-
文件描述符与文件流指针的关系
库函数与系统调用接口关系:上下级调用关系
文件流指针这个结构体中包含了文件描述符,当使用标准库进行io操作时, 其最终通过文件流指针找到文件描述符进而对文件进行操作
-
缓冲区
缓冲区是文件流指针为每个文件所维护的一个缓冲区属于用户态的缓冲区
#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main(){ printf("this is printf"); fwrite("fwrite", 6, 1, stdout); fprintf(stdout, "fprintf"); write(STDOUT_FILENO, "write", 5); return 0; }
-
文件描述符分配规则
在files_struct数组中, 找到当前没有被使用的最小的一个下标,作为新的文件描述符
-
使用dup2函数
#include <unistd.h> int dup2(int oldfd, int newfd); // 作用: // 将newfd重定向到oldfd所指向的文件 // 若newfd本身已有打开文件,重定向时则关闭已打开文件 // ex: #include <stdio.h> #include <fcntl.h> #include <unistd.h> int main(){ int fd = open("myfile", O_RDWR); dup2(fd, 1); printf("%d\n", fd); // fflush(stdout); close(fd); return 0; }
-
miniShell 增加重定向
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <sys/wait.h> #include <ctype.h> char buf[1024] = {0}; char *argv[32]; int argc = 0; void do_face() { printf("[san@localhost]$ "); fflush(stdout); memset(buf, 0x00, 1024); //%[^\n] 获取数据直到遇到\n //%*c 取出一个字符丢弃 if (scanf("%[^\n]", buf) != 1) { getchar(); } return ; } void do_parse() { char *ptr = buf; argc = 0; while(*ptr != '\0') { //当前位置非空白字符 if (!isspace(*ptr)) { argv[argc++] = ptr; while(!isspace(*ptr) && *ptr != '\0') { ptr++; } }else { *ptr = '\0'; ptr++; } } argv[argc] = NULL; return; } int main() { // ls >> > a.txt // int fd = open(a.txt); // dup2(fd, 1); // 将原先要写入到标准输出1中的数据,写入到指定文件中 while(1) { do_face(); //ls >> a.txt int redirect = 0; char *file = NULL; char *ptr = buf; while(*ptr != '\0') { if (*ptr == '>') { redirect = 1;//清空重定向 *ptr++ = '\0'; if (*ptr == '>') { redirect = 2;//追加重定向 *ptr++ = '\0'; } while(isspace(*ptr) && *ptr != '\0') { ptr++; } file = ptr; while(!isspace(*ptr) && *ptr != '\0') { ptr++; } *ptr = '\0'; } ptr++; } //解析流程:取出空白字符,获取程序名称和参数 do_parse(); int pid = fork(); if (pid < 0) { exit(-1); }else if (pid == 0) { //重定向必须在子进程当中完成 if (redirect == 1) { int fd = open(file, O_CREAT|O_WRONLY|O_TRUNC, 0664); dup2(fd, 1); }else if (redirect == 2) { int fd = open(file, O_CREAT|O_WRONLY|O_APPEND, 0664); dup2(fd, 1); } execvp(argv[0], argv); //防止子进程替换失败 exit(0); } wait(NULL); } return 0; }
-
-
文件系统
Linux系统的文件系统为ext2
![](C:\Users\some yuan\Documents\笔记\linux系统\文件系统.PNG)
-
文件储存流程
通过inode_bitmap 在inode table中找到空闲的inode节点,通过data_bitmap在数据块区域找到空闲数据块,将数据块位置信息,记录到inode节点中,将文件数据写入数据块中;将文件名和inode节点号写入父目录文件中
目录文件中存放了目录下文件信息的表, 表中记录了文件名,inode号
cat /a.txt 流程
在当前目录文件中查找文件名信息,通过文件名获取inode节点号,通过inode节点号,找到inode节点,进而访问数据块,读取数据进行输出
-
-
软链接/ 硬链接
-
软硬链接的创建
ln a.txt a.hard // 硬链接 ln -s a,txt a.soft // 软链接
软链接文件是一个独立文件,像是一个源文件的快捷方式文件 --inode节点号不同
硬链接文件是一个文件的另一个名字, 跟源文件并没有什么区别–inode节点号相同
删除源文件, 软链接失效(通过记录的源文件路径名查找源文件数据)
删除源文件,硬链接无影响 (通过inode节点找文件数据只是链接数-1)
软链接文件可以跨分区建立,硬链接不可以
软链接文件可以针对目录文件创建,硬链接不可以
-
-
静态库/动态库
-
生成静态库
-
将要生成静态库的文件编译为.o文件
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
-
生成静态库
ar -rc libmymath.a add.o sub.o mul.o div.o
ar -rc lib+库名 .o文件
-c 创建
-r 替换
-
查看静态库中的目录列表
ar -tv libmymath.a
t: 列出静态库中的文件
v: verbose 详细信息
-
使用
gcc main.c -L. -lmymath
-L 指定库的路径
-l 指定库名
目标文件生成后,静态库删除,程序依旧可以运行
-
-
生成动态库
-fPIC的全称是Posttion Independent code,用于生成位置无关代码。代码无绝对跳转,跳转都为相对跳转
即使不使用 -fPIC也可以生成.so文件,但是对于源文件有要求,因为不加fPIC编译的so必须要在加载到用户程序的地址空间时重定向所有表,这将导致不能引用其他地方的代码
添加fPIC实现真正意义上的多个进程共享so文件, 多个进程引用同一个PIC动态库时,可以共用内存。被调用的库在不同进程中的虚拟地址不同,但是操作系统会将其映射到同一块物理内存上
-
将要生成静态库的文件编译为.o文件
gcc -fPIC -c add.c -o add.o
gcc -fPIC -c sub.c -o sub.o
gcc -fPIC -c mul.c -o mul.o
gcc -fPIC -c div.c -o div.o
-
生成动态库
gcc --share add.o sub.o mul.o div.o -o libmymath.so
–share 生成一个共享库而不是可执行程序
-
因为gcc默认是动态链接 因此优先使用动态库生成可执行程序
库文件的默认查找路径/lib64 /usr/lib64
设置环境变量:LD_LIBRARY_PATH=. (库的运行路径)
-
*注意:*通常链接静态库生成可执行程序的时候,并不是使用-static静态链接,而是将静态库放到指定的路径下,然后使用gcc -L选项指定库的链接路径链接静态库生成可执行程序
-static 作用是可执行程序使用静态链接生成,不依赖任何动态库
-