1. 回顾库函数IO接口:
fopen,fwrite,fread,fseek,fclose
(1) FILE *fopen(char *pathname, char *mode);
pathname:文件路径名; mode:文件的打开方式
返回值:打开文件成功则返回一个FILE*文件流指针作为文件的操作句柄,失败返回NULL
r 只读方式打开文件,文件必须存在
r+ 读写方式打开文件,文件必须存在
w 只写方式打开文件,文件不存在则自动创建,存在则清空内容
w+ 读写方式打开文件,文件不存在则自动创建,存在则清空内容
a 追加写方式打开文件,文件不存在则自动创建,存在则写入数
据总是写入文件末尾
a+ 读+追加写方式打开文件,文件不存在则自动创建,存在则写
入数据总是写入文件末尾
b 二进制方式打开文件
在系统调用IO接口中并不区分文本和二进制数据,统一按照二进制
进行处理,但是库函数不一样,区分文本和二进制,默认文本操作,但
是有的文本一个字符占据多个字节.
例如:一个文件中有10个字节数据,但是读取的时候读取完毕后读取
大小是9,这个9表示的不是9个字节而是9个字符。
(2) size_t fwrite(const viod* data,size_t bsize,size_t nmem,FILIE *fp)
data:要写入文件的数据的所在空间首地址;
bsize: 块大小; nmem:块个数; bsize*nmem 要写入文件大小
fp:fopen返回的文件流指针,标识要操作哪一个文件.
返回值:返回实际完整操作的块个数.
注意:
r+/w/w+方式文件打开之后,文件的默认读写位置在文件起始,
如果文件中本身就有数据,就会覆盖,并且读写位置会随着数据的写入向后偏移;
a/a+方式打开文件之后,读默认是从文件起始读的,
但是如果写会使读写位置跳转到文件末尾,将数据追加到末尾
(3) size_t fread(void *buf, size_t bsize, size_t nmem, FILIE *fp)
buf:一块空间的首地址,用于存放从文件中读取的数据
bsize:块大小; nmem: 块个数; bsize*nmem 要写入文件大小
fp:文件流指针
返回值:成功返回实际操作的完整块个数,读取到文件末尾返回0;
出错了也返回0.
注意:
这个函数返回值存在多义性,返回0的时候无法直接确定是出错还是读到文件末尾了。
例如一个文件100字节,现在读取数据块大小200,块个数1,因为没有读取完整的一块,并且读取到了文件末尾,因此返回了一个0,这时候这个0无法确定出错了还是正常的。
因此,建议fread读取数据的时候,块大小设置为1,想要读取的长度
设置为块个数,这样返回值就能告诉我们读取了多少数据。
(4) int fseek(FILE *fp, int offset, int whence);
改变文件读写位置,跳转到哪里就从哪里开始读写
fp:文件流指针; offset:偏移量; whence:偏移起始位置
SEEK_SET-起始 / SEEK_CUR-当前 / SEEK_END-末尾
返回值:成功返回0;失败返回-1
(5) int fclose(FILE *fp);
关闭文件释放资源
2. 系统调用IO接口:
open,read,write,lseek,close
(1) int open(char *pathname, int flag, mode_t mode);
pathname:要打开的文件路径名
flag:文件打开标志—决定了文件的打开方式
必选标志: O_RDONLY / O_WRONLY / O_RDWR 只能选一个
可选标志:
O_APPEND-追加写 | O_CREAT-文件不存在则创创建 | O_TRUNC-文件存在则截断为0 | 0_EXCL-文件存在则报错
例如:
w+:读写+创建+截断 O_RDWR|O_CREAT|O_TRUNC
a+:读+追加写,创建 O_RDWR|O_APPEND|O_CREAT
mode:文件的权限,通常采用八进制数字设定 0777,如果使
用了O_CREAT就一定要有第三个参数
返回值:成功返回一个非负整数-文件描述符-是文件的操作句柄;
失败返回-1;
文件的创建权限是受到umask掩码影响的.
实际文件得到的权限 mode&(~umask)
mode_t umask(mode_t mask);
通常在程序起始阶段调用umask(0)将当前进程的创建掩码设置为0
(2) ssize_t read(int fd, void *buf, int len);
fd:open返回的文件描述符-操作句柄-表示操作哪个文件
buf:一块空间地址,用于存放读取到的数据
len:要读取的数据长度
返回值:成功返回实际读取到的数据长度; 失败返回-1;
(3) ssize_t write(int fd, void *data, int len);
fd:文件描述符-操作句柄;
data:要写入文件的数据的空间首地址;
len:想要写入文件的数据长度
返回值:成功返回实际写入文件的数据长度; 失败返回-1;
(4) off_t lseek(int fd, off_t offset, int whence);
fd:文件描述符;
offset:偏移量;
whence:偏移起始位置 SEEK_SET / SEEK_CUR / SEEK_END
返回值:成功返回跳转后的位置相对于文件起始位置的长度
(额外用法:跳转到文件末尾则返回文件长度);
失败返回-1
(5) int close(int fd);
关闭文件释放资源
3. 文件描述符:open返回的一个非负整数
文件描述符本质上就是内核中,进程打开的文件IO信息指针数组的一个下标
当我们通过描述符操作文件的时候,
在pcb中找到files_struct结构体指针,
进而找到结构,找到结构体中的数组,
以描述符作为下标获取到文件描述信息的地址,
通过描述信息操作文件。
打开文件:描述文件,然后将描述信息结构的地址添加到数组中,
返回对应下标作为文件描述符。
操作文件:通过描述在pcb的fd_arry数组中找到文件描述信息,
进而操作文件。
4. 重定向
改变数据的流向,让原本写入A文件的数据,不再写入A文件而是写入指定的B文件中
一个进程运行起来之后,默认会打开三个文件:
标准输入 标准输出 标准错误
stdin stdout stderr 文件流指针
0 1 2 文件描述符
(1)标准输出重定向原理
1描述符默认是显示器文件,但是一旦重定向,就会让1描述符不再指向显示器,而是指向指定的文件的描述信息,这时候,原本写入到1中的数据,就没有在写入显示器了,而是写入到指定的文件中。
所有代码都没有改版,只是改变了文件描述信息数组中下标对应的描述信息指针(描述符没变,改变了操作的文件)
(2)ls > >test.txt实现原理
ls是一个指令程序,运行时会启动进程,进程由shell创建,
shell创建了ls子进程之后只需要将这个进程的1号描述符对应的文件信息指针改变,
就可以实现不改变ls程序的情况下改变数据的流向。
注意:
程序替换,并不会初始化文件描述信息指针数组,因此提前修改了1号描述符的信息之后,程序替换,向1中写入数据就会把数据写入指定的文件。
(3)接口
int dup2(int oldfd, int newfd);
让newfd复制oldfd对应的描述信息,
成功则关闭newfd原来指向。
说白了dup2就是让两个描述符都指向oldfd所指向的文件,相当于是对newfd进行了重定向!
(4)在minishell中添加重定向功能的实现
>>:追加重定向
>:清空重定向
重定向实现: 在创建了子进程,程序替换之前,将标准输出给重定向到指定的文件上dup2(fd,1)
追加:打开fd的文件时使用O_APPEND
清空:打开fd文件时使用O_TRUNC
ls -l > > test.txt
- 捕捉输入
- 解析是否包含重定向(将命令截断得到 命令+重定向类型+重定向文件名)
- 解析指令(得到 命令名称,运行参数)
- 创建子进程
(1)子进程中先按照指定重定向类型打开重定向文件
(2)对子进程进行标准输出重定向,让标准输出1指向打开的文件
(3)程序替换 - 等待子进程退出
5. 文件描述符与文件流指针:
(1)文件描述符:struct FILE *
(2)文件流指针:int
注意:
类型不同,不能混用。
文件流指针是库函数的操作句柄,文件描述符是系统调用接口的操作句柄;
库函数和系统调用接口是一层上下级调用的关系:库函数中封装的就是系统调用接口。
dup2(fd, 1); 重定向1号描述符-标准输出,影响文件流指针
fwrite("hello bit\n",10, 1,stdout);
fwrite(用的是文件流指针) -> write(文件描述符)
stdout ->_fileno=fd; !!!文件流指针结构体中封装了文件描述符!!!
fwrite("hello world\n, 12, 1, stdout");
文件流指针中不仅有文件描述符,还有缓存区;
也就是说缓冲区不是系统调用中定义的,而是在上层封装的时候在流指针中封装的;
这个缓冲区所占空间-用户空间- - 称之为用户态缓冲区。
这也是为什么_exit系统调用接口退出进程前不刷新缓冲区的原因。
6. 动态库与静态库的生成与使用
(1) 生成
库的命名lib作为前缀,中间名称,最后.so作为动态库后缀, .a作为静态库后缀
(2) 库
已写好的功能性代码,打包而成的一个文件(不能有mian)
-
先将所有的.c编译汇编完成生成自己的.o二进制指令
gcc -fPIC -c $^ -o $@ -fPIC 产生位置无关代码
-
将所有的.o二进制指令文件打包到一起(生成的不是可执行程序,而是库)
gcc –shared $^ -o lib**.so -shared生成动态库 ar -cr lib**.a **.o **.o -cr生成静态库
(3) 使用:
1.链接生成可执行程序时使用:
(1) 使用-l来指定要链接的库名称(前提:库文件放到指定路径 下: /user/lib64)
export LIBRARY_PATH=${LIBRARY_PATH}:./
设置环境变量,将库文件所在目录加入到环境变量路径中。
(2) 使用gcc -L选项指定库文件的所在路径(常用于链接静态库-因为静态库没有依赖,运行时不用加载)
gcc main.c -o main -L./ -lchild
2.运行可执行程序时使用:
(仅限于动态库—因为只有链接动态库 运行程序的时候才需要加载动态库)
静态库使用时是把库中用到的代码直接放入到可执行程序,
动态库使用只记录函数符号信息,因此运行时才依赖。
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:./
设置环境变量中的库文件加载路径
总结:
1.将库文件放到指定路径 /usr/lib64
2.设置环境变量:LIBRARY_PATH + LD_LIBRARY_PATH
3.使用gcc -L选项
一个目录下,既有动态库,也有静态库,同名,gcc生成可执行程序时默认优先链接动态库
因为gcc的默认链接方式就是动态链接 – 链接动态库