一、C语言中的IO
在学习C语言之时,已经对IO函数有了一个基本的认识,了解到了他们是对文件做一些相关的操作,从而使得我们可以对文件进行一系列的操作,例如打开文件fopen,关闭文件fclose,往文件中写东西fwrite,从文件中读取内容fread,等等……
从上面的这些内容可知,对文件的操作也是需要一定的步骤,从而才能正确的实现我们的要求,下面来看一下C语言中文件操作函数的具体做法
FILE *fopen( const char *filename, const char *mode );
//函数fopen返回值为一个文件指针,他必定会指向一些文件
int fclose( FILE *stream );
//函数返回值的具体意义是关闭流的多少
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
//函数返回值的具体意义是具体写入文件的多少
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
//函数返回值的具体意义是具体从文件中读出多少信息
现在利用C语言中的函数具体实现一下
#include<stdio.h>
#include<string.h>
int main()
{
FILE * fp=fopen("myfile","w");
if(!fp)
{
perror("FILE of open");
return -1;
}
const char *msg="this is Sundary\n";
int count=5;
while(count--)
{
fwrite(msg,strlen(msg),1,fp);
}
fclose(fp);
return 0;
}
用过cat命令查看文件中的内容,如下所示
当然,利用fread从文件中读取内容与往文件中写东西是类似的,只是注意参数的改变,这两就不做一一演示了
注意:在有文件打开的时候,一定要在使用完成之后关闭文件,从而防止文件中的内容的泄露。
二、系统文件IO
系统文件函数是在C语言库中不带f的函数,因此这些函数都是不带缓冲的IO函数,这一些函数均称为系统调用函数。
系统调用函数与库函数之间的关系主要是如下所示:
int open(const char *pathname, int flags, mode_t mode);
//open函数第一个参数表示文件名称,第二个参数表示打开文件的方式,O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定⼀一个且只能指定⼀一个
O_CREAT : 若⽂文件不存在,则创建它。需要使⽤用mode选项,来指明新⽂文件的访问权限
O_APPEND: 追加写
第三个参数表示权限
ssize_t write(int fd, const void *buf, size_t count);
返回值为int,fd表示文件名,第二个参数表示写的内容,第三个参数表示写的大小
ssize_t read(int fd, void *buf, size_t count);
系统调用函数演示如下所示
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include<string.h>
6 int main()
7 {
8 int fd=open("file_open",O_RDONLY|O_CREAT,0644);
9 if(fd<0)
10 {
11 perror("open error");
12 return -1;
13 }
14 const char*msg="hello open\n";
15 int count=10;
16 //write
17 while(count--)
18 {
19 int sz=write(fd,msg,strlen(msg));
20 }
21 //read
22 char buf[1024];
23 while(1)
24 {
25 int rz=read(fd,buf,strlen(msg));
26 if(rz>0)
27 printf("%s",buf);
28 else
29 break;
30 }
31
32 close(fd);
33 return 0;
34 }
通过系统调用函数,可以达到与上述一致的目的,但是打开之时,也默认打开了一些文件,现在我们来验证默认打开了哪些
打开fd文件,默认从3号位置打开,因此在打开文件fd的同时,必定打开了其他文件,在此时关掉一些通道,查看文件是从多少打开
由此可知,描述文件的这个东西是从0开始的非负整数,,称打开文件之前打开的东西为文件描述符。
分别对应关系为
0———>标准输入
1———>标准输出
2———>标准错误
当其往标准输出中写东西时,可以直接写到显示器上,结果如下所示
代码如下所示
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include<string.h>
6 int main()
7 {
8 int fd=open("file_open",O_RDONLY|O_CREAT,0644);
9 printf("%d\n",fd);
10 if(fd<0)
11 {
12 perror("open error");
13 return -1;
14 }
15 const char*msg="hello open\n";
16 int count=10; //write
17 while(count--)
18 {
19 int sz=write(1,msg,strlen(msg));
20 }
21 close(fd);
22 return 0;
23 }
由上面可见,文件描述符就是一个整数,还是从0开始的整数,结合以前学过的知识,可以联想到,文件描述符与数组的下标类似,因此对于文件描述符的总结,可以如下:
Linux进程默认情况下会有3个缺省打开的⽂文件描述符,分别是标准输⼊入0, 标准输出1, 标准错误2.
0,1,2对应的物理设备⼀一般是:键盘,显⽰示器,显⽰示器
因此,由上面的图也可以看出,为什么当一个文件描述符被关闭之时,新打开的文件将占用,且替换原来位置文件描述符的作用,这就类似于重定向,用新文件的作用替换以前文件描述符的作用,因此,利用这个方法,也让我们认识到,如果要往一个其他文件中写东西,可以对应的关闭相应的文件描述符,利用重定向,往我们想写的地方写入数据。
对于文件描述符,具体总结如下:
文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调⽤用,所以必须让进程和件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵⼀一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,⽂文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
因此
常⻅见的重定向有:>, >>, <
在这里,在对重定向做一个详细的介绍
具体的解释就是如上所示,希望可以帮助到大家!!!
下面,我们再来说明一下系统调用接口和C语言库函数的区别:
系统调用所提供给用户的是直接而纯碎的高级服务,如果想要更加人性化,具有更符合特定情况的功能,那么就要我们用户自己定义,因此衍生了库函数,它把部分系统调用包装起来。比如当我们要用C语言打印一句话的时候,如果没有用到库函数printf,那么我们就需要自己实现就需要调用putc()和write()等这样一些系统函数。显得比较麻烦,所以系统调用是为了方便使用操作系统的接口,而库函数则是为了人们编程的方便。
现在在说明一下C库的缓冲区
主要分为三种
1、无缓冲
2、行缓冲:例如显示器
3、全缓冲:例如往文件中写东西
下面以一段代码说明系统调用与库函数的区别:
1#include<stdio.h>
2 #include<string.h>
3
4 int main()
5 {
6 const char*msg="hello printf\n";
7 const char*msg1="hello fwrite\n";
8 const char*msg2="hello write\n";
9
10 printf("%s",msg);
11 fwrite(msg1,strlen(msg1),1,stdout);
12 write(1,msg2,strlen(msg2));
13
14 fork();
15 return 0;
16 }
运行此函数,得到结果如下所示
因此,由上面的结果也可以得到以下的结果:
一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
printf fwrite 库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
但是进程退出之后,会统一刷新,写入文件当中。
但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
write 没有变化,说明没有所谓的缓冲。
上面就是对C库函数与系统调用函数做了一个简单的区别,以后如果有机会,将会对其做深入的了解。
三、文件系统
为了对文件在操作系统中的管理与存储位置,下面将介绍一些与文件有关的信息
1、利用stat 文件名,查看文件的具体信息
如下所示
2、为了能够更好的理解Inode,下面我们将介绍一下文件系统
我们都知道,内存是一个很大的块,为了更加方便操作系统管理这些块,因此我们将其分为一个一个的区来管理,这样就类似一个城市,为了方便管理者管理,我们将一个城市划分为好几个个区,从而更加方便了我们的管理,对内存的管理亦是如此,下面我们将介绍一个区是如何管理的。
每一个文件都有其属性信息(例如文件的大小,权限,所属者等信息),数据信息(文件中的内容),因此,将这样管理文件
3、文件的存储
如果要存储一个文件,现在超级块中找到一个空的位置,再去inode_map位图区中,将相对应的位置的bit位改为1,表示这个位置已经存储文件,在利用inode_map在文件的inode区中找到相对应的位置从而存储起来,inode中将存储在数据库想对应的块,下面将用一张图来具体表示
从上面的图我们也可以了解到,在平常我们拷贝一个文件时为什么要那么长的时间,因为它不仅要从超级块中找空的存储空间,还要修改对应的位图以及i节点表以及数据表,但是在删除一个文件,却非常节省时间,这是因为我们只需要需改inode_map即可,不需要注意后面的块,因此十分的节省时间。
总结一下创建一个新文件的步骤
1. 存储属性
内核先找到一个空闲的i节点(这⾥里是263477)。内核把文件信息记录到其中。
2. 存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,700。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。
3. 记录分配情况
文件内容按顺序300,500,700存放。内核在inode上的磁盘分布区记录了上述块列表。
4. 添加文件名到目录
新的文件名abc。linux如何在当前的目录中记录这个⽂文件?内核将入口(263477,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
四、理解硬链接与软链接
1、硬链接
为一个文件创建硬链接以及删除一个硬链接
用上面也可以看出,创建一个一个默认的硬链接数目是2,其原因是,进入到目录下,使用cd ..可以进入到上一级目录,因此其硬链接数目为2(这是我对其的理解,如果有什么偏差,希望大家及时指正)
总结:file3和h_file3的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode401702 的硬连接数为2。
我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则将对应的磁盘释放。
2、软链接
硬链接是通过inode引用另外一个⽂文件,软链接是通过名字引用另外一个文件,在shell中的做法
为一个文件创建软链接以及删除一个软链接
通过上面的学习,我们对硬链接与软链接有了更加深刻的认识,相信将更好的帮助我们今后的学习。
五、实现静态库与动态库
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
一个与动态库链接的可执行文件仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)。
动态库可以在多个程序间共享,所以动态链接使得可执⾏文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
1、静态库的链接
说明:ar是gnu归档⼯工具,rc表⽰示(replace and create)
ar -tv libmath.a
rw-r--r-- 0/0 683 Mar 24 21:24 2018 add.o
rw-r--r-- 0/0 687 Mar 24 21:24 2018 sub.o
-L 指定库路径
-l 指定库名
测试目标文件⽣生成后,静态库删掉,程序照样可以运行。
在这里,将静态库移到一个目录下,在链接之时,指出路径,也可以链接成功,这里就不一一验证了。
2、动态库的生成
shared: 表示生成共享库格式
fPIC:产⽣生位置无关码(position independent code)
库名规则:libxxx.so
动态库的使用
l:链接动态库,只要库名即可(去掉lib以及版本号)
L:链接库所在的路径.
利用下面的命令运行动态库,发现与之前在链接静态库的时候出现了一样的错误,因此,在运行动态库的时候,会默认在/usr/bin下的目录找库文件(系统默认),因此,在此时,需要设置其环境变量,让其默认在当前或者自己设定的库中查找。(一般不要尝试改变动态库,会造成不可逆转的错误)
gcc main.o -o main –L. -lmymath
运⾏行动态库
1、拷贝.so文件到系统共享库路径下, 一般指/usr/lib
2、更改 LD_LIBRARY_PATH:这块指系统加载动态库的路径
利用unset取消环境变量的设置
3、不利用环境变量,从而运行动态库
ldconfig 配置/etc/ld.so.conf.d/,ldconfig更新
有关基础IO的知识,大概就这么多,有不对的地方,希望大家多多帮助
只有不停的奔跑,才能不停留在原地!!!