一、文件操作
1、静态文件和动态文件
文件平时都是存放在块设备中的文件系统中的,我们把这种文件叫静态文件。
当我们去open操作这个文件 的时候,内核在进程中就会建立一个打开文件的数据结构记录一下我们操作这个文件的信息(内核也会在内存中申请一块内存将这个静态文件的内存从块设备中读取到内存的特定地址用于管理,这个时候就叫动态文件)
为什么要这样设计;因为块设备的读取操作不灵活,而内存则可以按字节读取,所以建立一个动态文件的机制便于管理。
2、文件描述符
文件描述符本质就是一个数字。这个数字就是与之前(内核在进程中就会建立一个打开文件的数据结构记录一下我们操作这个文件的信息)挂钩的。
作用就是用来区分一个程序打开的多个文件的。
3、读写文件的API使用
3.1、open
int open(const char *pathname, int flags);返回值就是文件描述符
注意;fd程序必须记录好,以后向这个文件的所有操作都要靠这个fd去对应这个文件,最后关闭文件时也需要fd去指定关闭这个文件。如果在我们关闭文件前fd丢掉了那就惨了,这个文件没法关闭了也没法读写了。
linux中的文件描述符fd的合法范围是0或者一个正正数,不可能是一个负数。可以用此来判断open打开文件操作是否成功
3.2、read
size_t read(int fd, void *buf. size_t count)
3.3、write
size_t write(int fd, const void *buf, size_t count)
3,4、open中的flags参数
读写权限:O_RDONLY O_WRONLY O_RDWR
打开存在并有内容的文件时:O_APPEND、O_TRUNC
O_APPEND在底层就是对文件指针多做了一个同步操作。当有多个fd执行同一个文件时如果是O_APPEND模式打开的,那么一个文件指针移动其他的也会同步,从而达到追加的效果。O_APPEND的操作时原子的,不能被打断的。
打开不存在的文件时:O_CREAT、O_EXCL
我们打开一个文件默认就是阻塞式的,如果你希望以非阻塞的方式打开文件,则flag中要加O_NONBLOCK标志。(只用于设备文件,而不用于普通文件)
O_SYNC write阻塞等待底层完成写入才返回到应用层,写到硬件再返回而不是缓存区。
无O_SYNC时write只是将内容写入底层缓冲区即可返回,然后底层(操作系统中负责实现open、write这些操作的那些代码,也包含OS中读写硬盘等底层硬件的代码)在合适的时候会将buf中的内容一次性的同步到硬盘中。这种设计是为了提升硬件操作的性能和销量,提升硬件寿命;但是有时候我们希望硬件不好等待,直接将我们的内容写入硬盘中,这时候就可以用O_SYNC标志。
3,5、lseek
off_t lseek (int fd, off_t offset, int whence);
文件指针;表示我们当前正在操作文件流的位置,因为这个指针不能直接被访问,从而提供了lseek这个api来访问。
off_t返回值是文件指针里文件最开头的偏移量,因此可以利用lseek得到文件大小
ret = lseek(fd, 0, SEEK_END);
offset参数是针对whence标志的偏移量
lseek构建空洞文件,便于多线程操作
3.6、fcntl函数
int fcntl(int fd, int cmd,/args/)
使用某些命令对fd进行操作。
如F_DUPFD这个cmd就是完成复制文件描述符的
4、linux系统如何管理文件
4.1、在静态文件中的inode(i节点)
操作系统如何访问硬盘拿到文件;
读取硬盘内容管理表——>
找到我们要访问的文件的扇区级别的信息——>
再用这个信息查询到真正存储内容的区域->
得到我们想要的文件;(注意操作系统最初拿到的是文件名)
再就是在硬盘管理中都是以文件为单位的,每个文件都有一个对应的inode数据结构节点来存储该文件的信息。
4.2、在动态文件中的vnode(v节点)
在进程中打开文件操作其实也是一个进程,然而每个进程都有一个数据结构来存储这个进程中的是所有信息这个叫进程信息表。
在表中就会有一个指针指向一个文件管理表,而文件管理表就是记录当前进程打开的所有文件及相关信息,也就是fd文件描述符。
我们通过fd文件描述符最终找到的就是一个已经被打开的文件的管理结构体vnode。
5、文件共享的实现
fd的自动分配原则,(0,1,2已经被占用了)
这就是文件共享,多个fd也就是文件表最后其实指向的是同一个文件V节点
文件共享的核心就是怎么得到多个fd指向同一个文件
同一进程多次打开同一文件
不同进程分别打开同一文件
使用linux系统提供的dup和dup2两个API完成对文件描述符的复制
int dup(int oldfd)不能指向新的fd,按操作系统分配原则自动分配
int dup2(int oldfd,int newfd)可以指定新的fd
可以利用dup2完成输出重定位。
linux命令行中>重定位其实本质就是open打开一个文件,close关闭标准输出stdout ,再利用dup2将打开文件的fd和fd为1的标准输出关联起来。
6、标准IO库
标准IO库相对于文件IO多了一层封装,则可以支持不同操作系统,多了一层缓冲机制则性能更好。其余功能上的都差不多
7、文件属性和其属性获取
7.1、文件类型
普通文件(- regular file)
目录文件(d directory)
字符设备文件(c character)
管道文件(p pipe)
套接字文件(s socket)
符号链接文件(l link)
7.2、常用文件属性获取
7.2.1、stat、fstat、lstat函数
int stat(const char *path, struct stat *buf)
int fstat(int fb,struct stat *buf)
int lstat(const char *path, struct stat *buf)这个与stat的区别是在符号链接文件,lstat查阅的是符号链接文件本身的属性,而stat是符号链接文件指向的文件属性
struct
stat是内核定义的一个结构体,在<sys/stat.h>中声明,所以我们可以用。这个结构体中的所有元素加起来就是我们的文件属性信息。
7.2.2 文件属性和文件权限获取
文件属性中的文件类型标志在struct stat结构体的mode_t st_mode元素中,这个元素其实是一个按位来定义的一个位标志(有点类似于ARM CPU的CPSR寄存器的模式位定义)。这个东西有很多个标志位共同构成,记录了很多信息,如果要查找时按位&操作就知道结果了,但是因为这些位定义不容易记住,因此linux系统给大家事先定义好了很多宏来进行相应操作。
st_mode中除了记录了文件类型之外,还记录了一个重要信息:文件权限。
access函数检查权限设置
就是可以在操作某个文件前判断是否有权限做这个操作
int access(const char*pathname, int mode);返回0则执行成功,返回-1则失败没有权限
mode;F_OK测试文件是否存在,R_OK, W_OK, X_OK
chmod/fchmode 权限修改设置
int chmod(const char*pathname, int mode);返回0则执行成功,返回-1则失败没有权限
chmod(argv[1], S_IRUSR|S_IWUSR|S_IXUSR).
umask 与文件权限掩码,
文件掩码是linux系统中维护的一个全局变量设置,umask的作用是用来设定我们系统中新建的文件的默认权限,
umask 0022对应文件权限 rwxr-xr-x
7.2.3、读取目录文件
opendir readdir函数
dirent结构体
7.2.4、不可重入函数和可重入函数
(一般是不可重入版本函数名_r)
可重入表示函数返回的内存可以再次操作,不可重入则表示不能修改只能读取。
二、高级IO
1、阻塞与非阻塞
阻塞;当前进程会一直阻塞等待在这里直到条件满足
非阻塞;不会等待,直接执行,因此执行结果可以会失败
如何实现非阻塞IO访问;打开时添加O_NONBLOCK标志,或者使用fcntl设置文件描述符属性。
2、解决并发式IO的问题
2.1、轮询式
就是使用while循环来处理,但是一直占用cpu影响效率
2,2、IO多路复用
就是在while(1)轮询式效率不高的基础上增加一个API来进行监控,提高效率。实际在这个api里面来while1并设置了睡眠,而不是我们之间while1的操作
select和poll
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
int main(void)
{
// 读取鼠标
int fd = -1, ret = -1;
char buf[200];
fd_set myset;
struct timeval tm;
fd = open("/dev/input/mouse1", O_RDONLY);
if (fd < 0)
{
perror("open:");
return -1;
}
// 当前有2个fd,一共是fd一个是0
// 处理myset
FD_ZERO(&myset);//将集合情况
FD_SET(fd, &myset);//将mouse1设备的fd设置到set中
FD_SET(0, &myset);//将键盘fd0标准输入设备fd设置进入
tm.tv_sec = 10;//设置超时时间,超过还未反应则提示失败
tm.tv_usec = 0;
//select内部循环遍历监督
ret = select(fd+1, &myset, NULL, NULL, &tm);
if (ret < 0)
{
perror("select: ");
return -1;
}
else if (ret == 0)
{
printf("超时了\n");
}
else
{
// 等到了一路IO,然后去监测到底是哪个IO到了,处理之
if (FD_ISSET(0, &myset))
{
// 这里处理键盘
memset(buf, 0, sizeof(buf));
read(0, buf, 5);
printf("键盘读出的内容是:[%s].\n", buf);
}
if (FD_ISSET(fd, &myset))
{
// 这里处理鼠标
memset(buf, 0, sizeof(buf));
read(fd, buf, 50);
printf("鼠标读出的内容是:[%s].\n", buf);
}
}
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
int main(void)
{
// 读取鼠标
int fd = -1, ret = -1;
char buf[200];
struct pollfd myfds[2] = {
0};
fd = open("/dev/input/mouse1", O_RDONLY);
if (fd < 0)
{
perror("open:");
return -1;
}
// 初始化我们的pollfd
myfds[0].fd = 0; // 键盘
myfds[0].events = POLLIN; // 等待读操作
myfds[1].fd = fd; // 鼠标
myfds[1].events = POLLIN; // 等待读操作
ret = poll(myfds, fd+1, 10000);
if (ret < 0)
{
perror("poll: ");
return -1;
}
else if (ret == 0)
{
printf("超时了\n");
}
else
{
// 等到了一路IO,然后去监测到底是哪个IO到了,处理之
if (myfds[0].events == myfds[0].revents)
{
// 这里处理键盘
memset(buf, 0, sizeof(buf));
read(0, buf, 5);
printf("键盘读出的内容是:[%s].\n", buf);
}
if (myfds[1].events == myfds[1].revents)
{
// 这里处理鼠标
memset(buf, 0, sizeof(buf));
read(fd, buf, 50);
printf("鼠标读出的内容是:[%s].\n", buf);
}
}
return 0;
}
2.3、异步IO
就是操作系统用软件实现的一套中断响应系统
异步IO的工作方法是:我们当前进程注册一个异步IO事件(使用signal注册一个信号SIGIO的处理函数),然后当前进程可以正常处理自己的事情,当异步事件发生后当前进程会收到一个SIGIO信号从而执行绑定的处理函数去处理这个异步事件。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
int mousefd = -1;
// 绑定到SIGIO信号,在函数内处理异步通知事件
void func(int sig)
{
char buf[200] = {
0};
if (sig != SIGIO)
return;
read(mousefd, buf, 50);
printf("鼠标读出的内容是:[%s].\n", buf);
}
int main(void)
{
// 读取鼠标
char buf[200];
int flag = -1;
mousefd = open("/dev/input/mouse1", O_RDONLY);
if (mousefd < 0)
{
perror("open:");
return -1;
}
// 把鼠标的文件描述符设置为可以接受异步IO
flag = fcntl(mousefd, F_GETFL);
flag |= O_ASYNC;
fcntl(mousefd, F_SETFL, flag);
// 把异步IO事件的接收进程设置为当前进程
fcntl(mousefd, F_SETOWN, getpid());
// 注册当前进程的SIGIO信号捕获函数
signal(SIGIO, func);
// 读键盘
while (1)
{
memset(buf, 0, sizeof(buf));
//printf("before 键盘 read.\n");
read(0, buf, 5);
printf("键盘读出的内容是:[%s].\n", buf);
}
return 0;
}
2.4、存储映射IO
利用mmpp函数完成内存映射,注意是共享而不是复制,
案例 LCD图片显示、IPC之共享内存
多线程也可以解决并发IO的问题