高级IO
同步IO
一般向磁盘写数据 利用write交给内核后 内核会保留数据返回成功(这个是非同步)
同步的话就是向磁盘中写数据 内核就要写到磁盘中 (并发的意思)(和线程的同步不一样的意思)
int open(const char *pathname, int flags, mode_t mode);
//O_SYNC
//int fsync(int fd);
//每次调用write还是有缓冲 write完之后调用fsync()函数
//确保数据写入磁盘
直接IO
用户层的缓冲 — 标准IO缓冲 ---- 内核缓冲 ---- 磁盘可能也有
//O_DIRECT 避免内核缓冲 直接到磁盘
缓冲IO
标准IO都是缓冲IO 行缓冲 块缓冲(写磁盘一个块就是一个文件)
标准IO是双拷贝的从用户拷贝到标准IO,再从标准IO拷贝到内核,例如fopen。
dd 命令 复制文件
dd if=/dev/zero of=a.out bs=1 count=1024*1024 //bs 写一个存一个 缓冲的好处
非阻塞和阻塞IO
man 2 open O_NONBLOCK 如果open漏了可以用 fcntl()设置
遇到阻塞会挂起,变成就绪态然后排队调度CPU(如果设置了事件通知就变成等待态,然后再变成就绪态)
非阻塞当没等到需要的事件的时候,有一个-1错误类型EAGIN(下次再来),可以加上IO通知(通知你什么时候来) 阻塞的话 就会返回EAGIN
可以加上IO通知
man 2 fcntl 对文件设置非阻塞 很多接口都会返回EINVAL非法的值
//针对多进程中 exce close
F_GETFD (void) //拿到一个bit掩码 对流操作
F_SETFD (int)
//下面的可以用来设置flag属性
F_GETFL (void) //拿到一个bit掩码 对flag操作
F_SETFL (int) //(O_RDONLY, O_WRONLY, O_RDWR O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC)
设置文件非阻塞 fcntl() 对文件设置非阻塞
int fcntl(int fd, int cmd, ... /* arg */ );
//第三个参数可选 第三个参数是第二个参数的参数
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
int main() {
int age = 0;
int flag = fcntl(0, F_GETFL);
flag |= O_NONBLOCK; //设置非阻塞
fcntl(0, F_SETFL, flag);
//sleep(2);
scanf("%d", &age); //scanf 会阻塞然后被挂起 内存置换出外存
printf("sss is %d yead old\n", age);
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ED9xWMBp-1617197684703)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1616331528902.png)]
IO多路复用
select
select()和pselect()允许程序监视多个文件描述符,等到一个或多个文件描述符对某些文件变为“就绪”状态 ,文件描述符是考虑因素如果可以执行相应的I / O操作而不会阻塞。
监控的数量有限制
-
select()没有sigmask参数(不能屏蔽信号),其行为与调用空sigmask的pselect()相同。
-
select()可以更新timeout参数以指示多少时间, 而pselect()监控一段时间
监视了三个独立的文件描述符集。
- 文件描述符将查看readfds中列出的字符,以查看是否有字符可用于阅读(更准确地说,是查看阅读是否会受阻;尤其是文件描述符也已在文件末尾准备好)。
- 文件描述符将监视writefds以查看是否有可用于写入的空间(尽管大写操作仍可能会阻止)。
- exceptfds中的文件描述符将是注意特殊情况。 (异常了)
退出时,每个文件描述符集都会被修改以指示哪个文件描述符实际更改了状态。 (因此,如果使用select()在循环中,必须在每次调用之前重新初始化集合。)成功后,select()和pselect()返回所有就绪的文件描述符的数量。时间过了返回零。不成功返回-1.
提供了四个宏来操作这些集合。
FD_ZERO()清除一个集合。
FD_SET()和FD_CLR()从集合中添加和删除给定的文件描述符。
FD_ISSET()测试文件描述符是否是集合的一部分;
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
//第一个是最大数量 + 1 第二三四参数 是三个监控集合 第五个设置时间
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
#include "head.h"
int main() {
int age;
fd_set rfds; //设置集合
struct timeval tv; //设置时间
tv.tv_sec = 5;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(0, &rfds);
int ret = select(1, &rfds, NULL, NULL, &tv); //开始监控可读集合
if (ret < 0) {
perror("select");
exit(1);
}
if (ret > 0) {
scanf("%d", &age);
} else {
age = 100;
}
printf("yease old %d\n", age);
return 0;
}
缺点:
时间复杂读是n^2 要用文件描述符一个一个去遍历找到改变的文件描述符
返回的是数量不是具体的那个文件描述符
用户态拷贝到内核态, 内核态拷贝到用户态 (可以再用户态做映射)
poll
加强版的select
成功时,将返回正数这是结构的数量,去遍历这个结构体数组
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// 第一参数是一个数组指针 第二个是一个大小 第三个是时间
struct pollfd {
int fd; //这个设置成负数事件就会被忽略
short events; //传入参数 用bit掩码表示 也可以为0
//POLLIN POLLOUT POLLPRI POLLRDHUP.... 具体看man
short revents; //传出参数 内核填充
};
epoll
只在linux上才有 用红黑树和mmap写的
epoll API执行与poll类似的任务:监视多个文件,以查看是否可以在其中任何一个上进行I / O,可以监控大量文件描述符。
为什么做高并发 ? mmap 做的内存映射
边缘触发 适合高并发
水平触发
//epoll_create()返回引用新epoll实例的文件描述符。后面的接口都用它, 当所有文件描述符都引用一个epoll实例已关闭,内核销毁该实例并释放
int epoll_create(int size); //创建一个epoll实例
//通过设置op可以把fd注册进epoll实例
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//op : EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; //比特掩码 具体看man
epoll_data_t data; // 存储与文件描述符相关
};
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
//第二第三是个数组