在C语言中我们学习到了文件的I/O,可以回顾一下:[(https://blog.csdn.net/mmwwxx123/article/details/81516082)
系统文件I/O
linux下所有设备都是以文件存在的,,可以说是一切皆文件,所以当我们需要用到这些设备的时候,首先就需要打开它们,下面我们来详细了解一下文件I/O操作。
用到的文件I/O有以下几个操作:打开文件、读文件、写文件、关闭文件等,对应用到的函数有:open、read、write、close、lseek(文件指针偏移)
- 打开文件
int open(const char *pathname, int flags);
int open(const char *pathname,int flags,mode_t mode);
参数:
a、pathname:打开或者创建的文件名字,如"text"
b、 flags:
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读、写打开
O_CREAT:若此文件不存在则创建它,使用O_CREAT时后面要跟文件的访问权限位,如O_CREAT,0777
O_APPEND:每次写时都追加到文件的尾端
O_EXCL :如果同时指定了O_CREAT,而文件已经存在,则出错,用此可以测试一个文件是否存在,如果不存在,则创建此文件,这使测试和创建两者成为一个原子操作
O_DSYNC:使每次write等待物理I/O操作完成,但是如果该写操作并不影响读取刚写入的数据,则不需等待文件属性被更新
O_NONBLOCK :如果path引用的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本次打开操作和后续的I/O操作设置非阻塞方式
O_NOCTTY:如果path引用的是 终端设备,则将该设备分配为此进程的控制终端
O_SYNC:使每次write要等待物理I/O操作完成,包括有该write引起的文件属性更新所需的I/O
O_TRUNC:如果文件存在,并且是常规文件而且以读写或者只写打开,则将其长度截断为0,如果文件是FIFO或终端设备文件,O_TRUNC标志被忽略,否则O_TRUNC不明确
O_DIRECTORY:如果pathname引用的不是目录,则出错
返回值:
成功:文件描述符
失败:-1
- 读文件
函数原型 ssize_t read(int fd, void *buf, size_t count);
参数:
a、fd:调用open后返回的文件描述符
b、buf:用来存放从文件中读到的数据的缓冲区
c、count:读取的字节数
返回值:
成功:读到的字节数,如果读到文件尾端,则返回0
失败:-1
- 写文件
函数原型 ssize_t write(int fd, const void *buf, size_t count);
参数:
a、fd:调用open后返回的文件描述符
b、buf:从来存放数据的缓冲区
c、count:写入数据的字节数
返回值:
成功:返回已写的字节数
失败:-1
- 关闭文件
函数原型 int close(int fd);
参数:
a、fd:调用open后返回的文件描述符
- 文件偏移
函数原型 off_t lseek(int fd, off_t offset, int whence);
参数:
a、fd:调用open后返回的文件描述符
b、offset 和参数whence有关,通常设置为0 (according to thedirective whence as follows)
c、whence:
SEEK_SET: 将文件的偏移量设置为距文件开始处offset个字节
SEEK_CUR:将文件的偏移量设置为其当前值加offset个字节,offset可为正或负
SEEK_END: 将文件的偏移量设置为文件长度加offset,offset可为正或负
返回值:
成功:返回新的文件偏移量
失败:-1
文件描述符 fd
对于内核而言,所有打开的文件都通过文件按描述符引用。文件描述符是一个非负整数。当打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,使用open/creat返回的文件描述符标识该文件,将其作为参数传送给read或write。
linux系统下文件描述符0是标准输入,1是标准输出,2是标准出错,所以一般打开文件的时候文件描述符都是从3开始。 这里read和write是不能格式化读取和写入。
当打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以让进程与文件关联起来。每个进程都有一个指针*files,指向一张表fiiles_struct,该表包含一个指针数组,每个元素都是一个指向打开文件的指针。所以,本质上,文件描述符就是该数组的下标。
文件描述符的分配规则:
在file_struct 数组中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
重定向
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/stat.h>
4 #include<string.h>
5 #include<sys/types.h>
6 #include<fcntl.h>
7 int main()
8 {
9 close(1);
10 int fd=open("myfile",O_WRONLY|O_CREAT,0644);
11 if(fd<0)
12 {
13 perror("open");
14 return 1;
15 }
16 printf("fd: %d\n",fd);
17 fflush(stdout);
18 close(fd);
19 exit(0);
20 }
运行程序后,发现本来输出到显示器上的内容,输出到了文件myfile中,这就叫输出重定向。
常见的重定向有:>,>>,<.
重定向的本质如图:
printf一般往stdout中输出,但是stdout底层访问文件时,找的还是fd:1,但此时,fd:1下标所表示的内容已经变成了myfile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而实现输出重定向。
函数dup和dup2
这两个函数都可用来复制一个现存的文件描述符,返回的新文件描述符与参数fieldes共享同一个文件表项.
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。用dup2则可以用fnewfd参数指定新描述符的数值。如果newfd已经打开,则先将其关闭。如若oldfd等于newfd,则dup2返回newfd,而不关闭它。
文件系统
我们用ls -l可以查看文件信息:
这些信息都有:模式,硬链接数,文件所有者,组,大小,最后修改时间,文件名。
相应的,我们用stat命令也可以查看信息:
因此在文件系统中它们相对应的如图:
创建一个新文件有以下步骤:
- 存储属性
内核先找到一个空闲的i节点(664231),内核把文件信息记录在其中。 - 存储数据
内核找到三个空闲块:例如:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。 - 记录分配情况
文件内容按顺序存放。内核在inode上的磁盘分布区记录了上述块列表。 - 添加文件名到目录
新的文件名abc,内核将入口(例664231,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件内容及属性连接起来。
由以上可知:真正找到磁盘上文件的并不是文件名,而是inode。
硬链接
硬连接指通过索引节点来进行连接。在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在Linux中,多个文件名指向同一索引节点是存在的。一般这种连接就是硬连接。硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。
由于硬链接是有着相同 inode 号仅文件名不同的文件,因此硬链接存在以下几点特性:
- 文件有相同的 inode 及 data block;
- 只能对已存在的文件进行创建;
- 不能交叉文件系统进行硬链接的创建;
- 不能对目录进行创建,只可对文件创建;
- 删除一个硬链接文件并不影响其他有相同 inode 号的文件。
软链接
另外一种连接称之为符号连接(Symbolic Link),也叫软连接。软链接文件有类似于Windows的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。
软链接与硬链接不同,若文件用户数据块中存放的内容是另一文件的路径名的指向,则该文件就是软连接。软链接就是一个普通文件,只是数据块内容有点特殊。软链接有着自己的 inode 号以及用户数据块。因此软链接的创建与使用没有类似硬链接的诸多限制:
- 软链接有自己的文件属性及权限等;
- 可对不存在的文件或目录创建软链接;
- 软链接可交叉文件系统;
- 软链接可对文件或目录创建;
- 创建软链接时,链接计数 i_nlink 不会增加;
- 删除软链接并不影响被指向的文件,但若被指向的原文件被删除,则相关软连接被称为死链接(即 dangling link,若被指向路径文件被重新创建,死链接可恢复为正常的软链接)。
我们用ln 创建一个硬链接文件;用 ln -s创建一个软连接文件:
从上面的结果中可以看出,硬连接文件b.txt与原文件a.txt的inode节点相同,然而符号连接文件的inode节点不同。
当删除原始文件a.txt后,硬连接b.txt不受影响,但是符号连接a.s文件无效.
动态库和静态库
静态函数库
这类库的名字一般是libxxx.a;利用静态函数库编译成的文件比较大,因为整个 函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。
生成静态库:
add.h
1 #ifndef __ADD_H_
2 #define __ADD_H_
3 int add(int a,int b);
4
5 #endif // _ADD_H_
add.c
1 #include"add.h"
2
3 int add(int a,int b)
4 {
5 return a+b;
6 }
sub.h
1 #ifndef __SUB_H_
2 #define __SUB_H_
3 int sub(int a,int b);
4
5 #endif // _SUB_H_
sub.c
1 #include"sub.h"
2
3 int sub(int a,int b)
4 {
5 return a-b;
6 }
main.c
1 #include<stdio.h>
2 #include"add.h"
3 #include"sub.h"
4
5 int main()
6 {
7 int a=10;
8 int b=20;
9 printf("add(10,20)=%d\n",a,b,add(a,b));
10 a=100;
11 b=20;
12 printf("sub(10,20)=%d\n",a,b,sub(a,b));
13
14 }
动态函数库
这类库的名字一般是libxxx.so;相对于静态函数库,动态函数库在编译的时候 并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。
生成动态库:
其中运行动态库有三种方法;
方法一:(如图黄框)更改LD_LIBRARY_PATH。
方法二:拷贝.so文件到系统共享库路径下:/usr/lib。
方法三:ldconfig 配置 /etc/ld.so.conf.d/ ,ldconfig更新。