Lin9ux下的基础I/O
C语言阶段
- 在学习C语言的时候,我们要实现程序的I/O操作,需要调用fopen()和fclose来打开和关闭文件,fopen()成功返回FILE*的文件指针,
- 利用fread和fwrite函数来进行文件的读写操作。当然我们还学过fseek重置文件指针,ftell,rewind等函数
- 我们还知道,C会默认打开流,stdin,stdout,stderr,并且这三个流都是FILE*类型的,即文件指针。
系统编程阶段
当我们进入系统编程之后,我们可以同样的调用系统接口,来实现I/O操作。
系统接口open
int open(const char*pathname, int flags);
int open(const char* pathname,int flags,mode_t mode)
pathnamm:要打开文件名称
flag:O_RDONLY(只读),O_WRONLY(只写),O_RDWR(可读可写),O_CREAT(文件不存在则创建之,并且要加上mode权限)
3.write
ssize_t write(int fd, const void *buf, size_t count);
fd:对应操纵文件的文件描述符,
buf:源数据存放内存
count:一次写入多少个字节
返回值:实际写入字节数read
ssize_t read(int fd, void *buf, size_t count);
fd:对应文件的文件描述符;
buf:写到的目的内存;
count:一次写入的字节数;
返回值:实际读取的字节数- close
int close(int fd);
关闭对应文件描述符为fd的文件。 - 我们知道,c提供的f#类函数都是库函数,调用成功返回文件指针,而open,close,write,read属于系统调用接口,在讲操作系统的作用的时候,讲过下面这张图。
- 不难看出,c库函数都是封装了系统调用的接口,方便二次开发。我们又知道linux下一个进程的PCB中有files*的一个字段,描述一个进程打开文件的情况,而这个指针指向一个files_struct的结构体表,这个表中最重要的就是保护一个指针数组,每个数组元素都指向打开的文件,我们通常就利用数组的下标来操纵对应位置的元素,我们把这里的下标称之为文件描述符,我们都知道数组下标从0开始的,所以每一个进程都默认打开0,1,2三个文件描述符表示的文件,分别代表标准输入,标准输出,标准出错。如下图
这里我们就来练习一下这几个函数:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<string.h>
5 #include<fcntl.h>
6 #include<sys/stat.h>
7 int main()
8 {
9
10 int fd=open("myfile",O_RDWR|O_CREAT,0644);
11 if(fd<0)
12 {
13 perror("open");
14 return -1;
15 }
16 int count=5;
17 char *buf="hello,world\n";
18 while(count--)
19 {
20 write(fd,buf,strlen(buf));
21 }
22 char buf2[1024];
23 while(1)
24 {
25
26 ssize_t s=read(fd,buf2,strlen(buf)-1);
27 if(s>0)
28 {
29 printf("myfile if :%s\n",buf2);
30 }
31 else
32 {
33 break;
34 }
35 }
36 close (fd);
37 return 0;
- 既然现在我们了解了系统编程的I/O函数,已经文件描述符的概念,我们就知道,一个进程默认会打开0,1,2三个分别代表标准输入,标准输出,标准出错的文件描述符,而系统默认的输出就是屏幕,默认的输入就是键盘,如果讲原本输出到屏幕的数据输出到指定地方,这就叫做输出重定向.就拿输出重定向来举例,printf()默认是往屏幕上面打印数据,再调用时,底层就会找到1号文件描述符,我们只需要关闭1号文件描述符,然后用我们创建的myfile代替他,以后调用printf()函数就会找到1号文件描述符对应的内容,即往myfile中输出数据,
我们来实现一个小程序,使原来往屏幕上打印的数据,输出到文件中。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<string.h>
5 #include<fcntl.h>
6 #include<sys/stat.h>
7 int main()
8 {
9 close(1);
10 int fd=open("myfile",O_RDWR|O_CREAT,0644);
11 if(fd<0)
12 {
13 perror("open");
14 return -1;
15 }
16
17 printf("how are you?\n");
18 close(fd);
}
原本输出到屏幕上的数据就被输出到文件里面了
FILE结构和文件描述符
- 因为IO函数和系统调用对应,所以本质上还是操控的是文件描述符。所以再FILE结构体里面肯定封装了文件描述符。
- 一般的C库函数写入文件都是全缓冲的,写入显示器是行缓冲的。
- 而我们的系统调用接口是没有自带缓冲区的。
静态库和动态库
要了解静态库和动态库首先我们来了解一下文件系统,来看一下文件系统,每一个当我们使用ls -l 选项列出文件信息的时候,能看到文件的模式,硬链接数,文件所属者和所属组,当然还有文件大小和文件最后被修改时间,以及文件名。
但是每一个文件再计算机中如何存储的,我们来看下面这张图
超级快:保存着文件系统本身的结构信息。
i节点表:存放着文件的属性,如大小,所有者,最后修改时间
数据区:存放着文件的内容在查找一个文件的时候,并不是利用文件名来找,而是用文件名对应的inode(其实在linux中一个inode可以对应多个inode,)
- 我们可以使用ln命令来使两个文件对应同一个inode。
- 我们可以看出, 1 和 2 文件的 元信息中有 一列变成了2.
- 我们把通过inode引用另外一个文件叫做硬链接,把通过文件名引用另一个文件叫做软链接
静态库
程序在编译链接的过程中,把库的代码全部链接到可执行文件中,运行期间不需要静态库,这样造成可执行文件太大,并且效率低。
静态库命名规则 libXXX.a(lib是前缀,.a是后缀,XXX是库名)
生成方法
- 先使用gcc -c选项使目标文件在汇编完成后结束生成.o文件
- 使用ar -rc 库名 目标文件 来生成动态库
.
链接方法
- gcc man.c -L . -l mymath
-L选项后面加上库的路径,-l选项后加上库名,注意删除掉前缀后缀
动态库
程序在运行期间才回去链接到动态库的代码,多程序 共享使用库的代码。一个动态库链接的可执行程序仅仅包含它用到的函数的入口地址,而不是外部函数所在目标文件的整个机器码。节省了资源规则:libXXX.so
动态库命名
生成方法
gcc -shared表示生成共享库格式
gcc -fPIC表示生成位置无关的码
链接方法
gcc man.c -L . -l mymath