一.系统所提供的IO接口
1.open()
#include <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);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
mode参数表示设置文件访问权限的初始值,和用户掩码umask有关,
比如0644表示-rw-r–r–,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,详见open(2)的Man Page。
要注意的是,有以下几点:
1. 文件权限由open的mode参数和当前进程的umask掩码共同决定。
2. 第三个参数是在第二个参数中有O_CREAT时才作用,如果没有,则第三个参数可以忽略
返回值:
成功:新打开的文件描述符
失败:返回-1
2.read函数
//功能: 从fd文件中读取数据到buf所指向的空间,该空间大小为len
//返回值为:实际读取的字节数
int read(int fd, char *buf, size_t len);
3.write函数
//功能:往fd所指的文件中写入数据,数据的起始位置为buf,大小为len
int write(int fd, const char *buf, size_t len);
4.close函数
int close(int fd);
fd表示文件标识符
返回值:返回0表示成功,返回-1表示失败
二.文件描述符fd
- 通过对open函数的学习,我们知道了文件描述符就是一个整数
- Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
- 0,1,2对应的物理设备一般是:键盘,显示器,显示器
文件描述符与文件流指针的区别
fd只是一个整数,在open时产生,起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针file。
- open:文件描述符的操作(如:open)返回的是一个文件描述符(int fd),内核会在每个进程空间中维护一个文件描述符表,所有打开的文件都将通过,此表中的文件描述符来引用。
- fopen:流(如:fopen)返回的是一个文件指针(即指向FILE结构体的指针),FILE结构是包含有文件描述符的,fopen可以看做是open(fd直接操作的系统调用)的封装,它的优点是带有I/O缓存。
文件描述符与文件流指针的关系
库函数与系统调用接口关系:上下级调用关系
文件流指针这个结构体中包含了文件描述符,当使用标准库进行io操作时, 其最终通过文件流指针找到文件描述符进而对文件进行操作
三.重定向
1.文件描述符的分配规则
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
- 发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
close(1);
int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);
close(fd);
exit(0);
}
此时,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。
2.使用 dup2 系统调用重定向
函数原型如下:
#include <unistd.h>
int dup2(int oldfd, int newfd); //将newfd的内容拷贝到oldfd
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("./log", O_CREAT | O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
close(1);
dup2(fd, 1);
for (;;) {
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
break;
}
printf("%s", buf);
fflush(stdout);
}
return 0;
}
3.缓冲区问题
#include <stdio.h>
#include <string.h>
int main()
{
const char *msg0="hello printf\n";
const char *msg1="hello fwrite\n";
const char *msg2="hello write\n";
printf("%s", msg0);
fwrite(msg1, strlen(msg0), 1, stdout);
write(1, msg2, strlen(msg2));
fork();
return 0;
}
运行结果:
hello printf
hello fwrite
hello write
对进程实现输出重定向 ./hello > file , 我们发现结果变成了:
hello write
hello printf
hello fwrite
hello printf
hello fwrite
- 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
- printf fwrite 库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后。但是进程退出之后,会统一刷新,写入文件当中。
- 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
- write 没有变化,说明没有所谓的缓冲。
四.动态库和静态库
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
- 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
1.生成静态库
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
2.生成静态库
ar -rc libmymath.a add.o sub.o mul.o div.o
ar -rc lib+库名 .o文件
-c 创建
-r 替换
3.查看静态库中的目录列表
ar -tv libmymath.a
t: 列出静态库中的文件
v: verbose 详细信息
4.使用
gcc main.c -L. -lmymath
-L 指定库的路径
-l 指定库名
目标文件生成后,静态库删除,程序依旧可以运行
2.生成动态库
- -fPIC的全称是Posttion Independent code,用于生成位置无关代码。代码无绝对跳转,跳转都为相对跳转
- 即使不使用 -fPIC也可以生成.so文件,但是对于源文件有要求,因为不加fPIC编译的so必须要在加载到用户程序的地址空间时重定向所有表,这将导致不能引用其他地方的代码
- 添加fPIC实现真正意义上的多个进程共享so文件, 多个进程引用同一个PIC动态库时,可以共用内存。被调用的库在不同进程中的虚拟地址不同,但是操作系统会将其映射到同一块物理内存上
1.将要生成静态库的文件编译为.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
2.生成动态库
gcc --share add.o sub.o mul.o div.o -o libmymath.so
–share 生成一个共享库而不是可执行程序
-
因为gcc默认是动态链接 因此优先使用动态库生成可执行程序
-
库文件的默认查找路径/lib64 /usr/lib64
-
设置环境变量:LD_LIBRARY_PATH=. (库的运行路径)