Linux笔记-1

Linux学习相关的笔记记录(基础概念 & io多路复用 & mmap)

基础概念

  • 用户空间 / 内核空间

    操作系统都是使用的虚拟的存储器,x位操作系统对应的寻址空间(虚拟存储空间)是$2^x$

    操作的核心是内核,独立于普通的应用程序(可以理解为root?),为了保证内核的安全,系统划分为两个部分,内核&用户空间

  • 进程切换

    内核能够挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行

    进程是在操作系统内核的支持下运行的,切换进程是非常消耗资源的

  • 进程阻塞

    由于期望的事件非发生,或者等待完成,系统会将自己的运行状态改变成阻塞的状态。阻塞时进程自身的一种主动行为,只有处于运行态的进程才能够转换为阻塞状态

    在阻塞状态下,是不占用CPU资源的。

  • 文件描述符

    用于表述指向文件引用的抽象化概念,是一个索引值(表示为一个非负整数),指向内核中每一个进程所维护的该进程打开文件的记录表,打开一个现有文件或者创建一个新文件的时候,内核就向该进程返回一个文件描述符

  • 缓存IO

    又称标准IO,操作系统将IO的数据缓存在文件系统的页缓存中,即数据先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

I/O多路复用(select、poll、epoll)

五种I/O模型

  • 阻塞IO
  • 非阻塞IO
  • IO多路复用
  • 信号驱动IO

上述都属于同步IO,读写事件就绪之后自己负责读写,整个读写过程是阻塞的。

->select、poll、epoll的本质上也是同步IO

  • 异步IO

select

函数原型:int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)

参数说明:

maxfdp – 等待测试的文件描述字个数,是待测试的最大描述字加一

fd_set存放相关的文件描述符(文件句柄),这里指定读、写和异常条件的文件描述符集合

timeout – 等待制定的文件描述符集合中的任何一个就绪可以花费多少时间

返回值: >0 就绪描述符的个数 0超时 -1出错

运行机制:

fd_set实际是long类型的数组,元素都能够与一个打开的文件句柄建立联系,内核会根据io状态修改fd_set的内容,来通知调用了selcet进程哪一个句柄现在可以操作

类似于同步阻塞请求。同时添加了监控操作。但是可以在一个线程里面同时处理多个socket 的io请求 -> 在select里面注册多个,然后读取被激活的socket进行处理。

问题:

fd_set需要从用户态拷贝到内核态 / 调用select时需要在内核遍历传递所有的fd_set / 大小有显示(1024),防止拷贝带来的性能损坏

poll

与select相似,唯一的差别是描述文件从fd_set转换为了pollfd,具体定义如下

1
2
3
4
5
typedef struct pollfd {
int fd; // 需要被检测或选择的文件描述符
short events; // 对文件描述符fd上感兴趣的事件
short revents; // 文件描述符fd上当前实际发生的事件
} pollfd_t;

使得其支持的集合不局限于1024

函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数说明:

fds : 用于存放需要检测其状态的socket描述符,并且调用poll函数之后fds数组不会被清空;一个pollfd结构体表示一个被监视的文件描述符,通过传递fds指示 poll() 监视多个文件描述符。其中,结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域,结构体的revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域

nfds :记录描述符的总数量

返回值:>0 就绪描述符的个数 0超时 -1出错

epoll

使用一个文件描述符管理多个描述符,将关心的文件描述符的事件存放在内核的一个事件表里面,这样就只需要进行一次用户空间和内核空间的copy即可。

相关函数:

  1. int epoll_create(int size);

    创建一个epoll句柄,size代表内核要监听的描述符的数量,调用成功的时候返回一个描述符,否则返回-1

    内核在文件系统内创建了一个file的节点,内核cache中新建了一个红黑树用于存储ctl方法传来的内容,同时创建一个双向链表存储就绪的事件。wait调用的时候只需要观察这个双向链表有没有数据即可。如果没有对应的数据就继续进入睡眠,不唤醒。

  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    注册要监听的事件类型。四个参数解释如下:

    • epfd 表示epoll句柄
    • op表示fd操作类型,有如下3种
      • EPOLL_CTL_ADD 注册新的fd到epfd中
      • EPOLL_CTL_MOD 修改已注册的fd的监听事件
      • EPOLL_CTL_DEL 从epfd中删除一个fd
    • fd 是要监听的描述符
    • event 表示要监听的事件

    epoll_event 结构体定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
    };

    typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
    } epoll_data_t;

    添加时事件会与蛇鞭建立对应的回调关系,即callback,将这个部分放在双向链表中。

  3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

    等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0。

    • epfd 是epoll句柄
    • events 表示从内核得到的就绪事件集合
    • maxevents 告诉内核events的大小
    • timeout 表示等待的超时事件

    检查是否有事件链接的时候,只需要检查事件池里面的双链表中是否有epitem元素,不为空才将对应的事件复制到用户态内存中(一般拷贝到mmap存储的 IO映射区,即共享内存)

epoll主要是处理大批量文件描述符(poll的进阶版),显著提高程序在大量并发连接中只有少量活跃情况下的CPU利用率,在获取事件的时候,只需要遍历哪些已经被内核IO事件异步唤醒而加入的Ready队列的描述符集合,操作的成本又进一步的下降了。

epoll还有不同的一个部分就是提供了水平触发 / 边缘触发的方式,边缘触发方式能够减少epoll_wait的引用,提高效率

  • 水平触发

    监测到某事件就绪的时候通知到应用程序的时候,程序可以不立刻处理该事件,再下一次调用该方法的时候,会再次通知该事件

  • 边缘触发

    检测到某事件就绪的并通知到 应用程序的时候,程序必须立刻处理该事件,若不处理,再下一次调用该方法时,不会再通知该事件。除非将该操作符修改为未就绪状态。减少了触发次数

    即只有在未就绪 -> 就绪这个状态跳转的时候通知方法

这两种方法可以等价于信号的两种传输模式,一种是在脉冲点在1的状态就会一直发送一个sign,而另外一种只有在跳变的时候发送信号(0 -> 1)

mmap

概念

一种内存映射文件的方式,将一个文件或者其他的对象映射到进程的地址空间。实现这样的映射关系之后,就可以通过指针的方式读写这段内存,同时同步操作到对应的文件磁盘上,就完成了对文件的操作。实现了文件共享。

概念图类似如下:

200501092691998

映射原理

  1. 进程启动映射过程,创建虚拟映射区域
  2. 调用内核空间的系统调用函数mmap,事件物理地址与进程虚拟地址的映射关系
  3. 进程发起对映射空间的访问,引发缺页异常,实现文件内容到主存的拷贝

好处

  1. 减少了拷贝次数
  2. 实现了用户空间和内核空间的高效交互方式,修改操作能够及时被刷新
  3. 提供共享内存和相互通信的方式
  4. 实现高效的大规模数据传输

与常规文件操作的区别

  • 常规的文件系统操作

    1. 进程发起请求
    2. 内核查询进程文件符表,找到文件的信息和inode
    3. 该inode是否已经存在在页缓存中,若存在直接返回
    4. 不存在,直接定位到文件磁盘的地址,将数据从磁盘复制到页缓存,再发起读页面过程,将结果发送给用户进程

    读取和写入都是两次数据拷贝过程

  • 通过mmap发起操作

    参见上方,可得只有一次数据拷贝过程

注意事项

  1. 映射大小是物理页大小的整倍数
  2. 内核可以跟踪被内存映射的底层对象文件的大小,可以合理访问在当前文件大小内也在当前内存映射区内的字节。也就是说,若文件的大小在变化,若在映射的范围内,进程都可以合理地得到数据。
  3. 文件关闭后,在映射建立没有被解除的前提下都仍旧存在。映射的是磁盘的地址。同时有效地址空间不完全受限制与被映射的文件大小。

参考