【摘要】:通知链是为内核各个模块异步交互服务的,只能够在内核的子系统之间使用,而不能够用于内核与用户空间之间进行事件的交互。head是由事件通知者提供,而block是由事件接收者向事件通知者注册的。
1ᤫ异步IOAIO
除了同步数据读写之外,内核还提供了异步读写功能,对于VFS文件相关的接口是aio_read和aio_write等。AIO帮助用户程序提高性能以提高整体CPU和IO设备的利用率,特别是高IO负载的效率。在服务型应用中比较广泛的应用,比如各种代理服务器,数据库,流服务器等等。
AIO可以一次性发出大量的read/write请求,请求完成后统一返回。这样在用户程序的角度减少了因同步操作产生的负载,相当于应用层的数据批处理功能。
Linux内核为AIO提供了系统调用来完成异步IO的功能,相关调用如下:
●io_setup();为当前进程建立异步IO上下文。
●io_submit();提交一个或者多个异步IO操作。
●io_getevents();获得完成的异步IO的状态。
●io_cancel();取消某个IO操作。
●io_destroy();销毁异步IO上下文。
下面提供一个简单的例子,便于了解如何使用异步IO。具体如下:



从应用代码可见,异步IO更适合直接对块设备进行操作。通过进行一次大量的异步读写来减少同步开销,从而提高效率,这种场景更适合使用块设备的服务,如数据库、流服务等。
异步IO的内核实现框架如图5-36所示。

图5-36 异步IO内核实现框架
从图5-36可见,每个异步IO的上下文都包含真正执行IO操作的实体work(在worker线程上执行),而IO操作的完成情况通过ring buffer实现内核与用户之间交互。
相应的执行实体初始化是在分配IO上下文的函数ioctx_alloc来进行的,具体如下:

在需要执行IO操作时,会调度work执行aio_kick_handler。其中,调用aio_run_iocb对每个kiocb进行操作,调用ki_retry执行具体的操作。而kiocb在创建时会根据具体的IO操作通过aio_setup_iocb分配合适的操作接口给ki_retry。读写操作通常设置为aio_rw_vect_re-try,下面来分析一下aio_rw_vect_retry的实现:


从代码中可见,其主要是通过文件的异步接口来执行所有的异步请求,而相应的异步接口通常由具体的文件系统提供,如ext2文件系统的ext2_file_operations中就包含各种异步操作接口。具体的文件系统后续的操作在块设备层已经进行了介绍。可见Linux内核已经为异步IO提供了完整的上层框架,对于具体的操作则是通过VFS中的异步接口完成的。这样将具体的文件IO操作转移到异步IO框架中执行,用户层则不需要同步等待事件的完成,从而在需要进行大量操作时整体的性能要高很多。(www.chuimin.cn)
2ᤫ通知链notifier
Linux内核的各个子系统功能上都是相互独立的,但是各个子系统之间同样需要交互,例如某个子系统可能对其他子系统产生的事件感兴趣。如何让各个子系统之间能够方便地通知相关的事件并进行合适的操作,这属于横切的异步功能。Linux内核为了满足该需求设计了通知链的机制。通知链是为内核各个模块异步交互服务的,只能够在内核的子系统之间使用,而不能够用于内核与用户空间之间进行事件的交互。
通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事件发生时,链表上所有节点对应的函数就会被执行。所以通知链表有一个通知方与很多接收方。在通知这个事件时所运行的函数由接收方决定,就是接收事件方注册了回调函数,在发生某个事件时回调函数就得到执行。系统运行时通知链的状态如图5-37所示。
从图5-37可见,其中包含两个主要的实体,一个是head,另一个是block。head是由事件通知者提供,而block是由事件接收者向事件通知者注册的。
Linux内核提供与通知链相关的结构体如下:

图5-37 通知链运行时状态图

从中可见,有一个与block相关的结构notifier_block,有四个head相关的结构xxxx_noti-fier_head。为什么有四种事件通知者呢?这是由于内核事件本身可能在不同的上下文中发生,通常在事件发生时就要进行事件通知,而对通知的回调操作会有限制,Linux内核就直接将这些限制通过通知者的结构实现,这样依据不同的情况产生了四种head,细节如下:
●atomic_notifier_head通知链block的回调函数(当事件发生时要执行的函数)只能在中断上下文中运行,不允许阻塞。
●blocking_notifier_head通知链元素的回调函数在进程上下文中运行,允许阻塞。
●raw_notifier_head对通知链元素的回调函数没有任何限制,所有锁和保护机制都由调用者维护。
●srcu_notifier_head可阻塞通知链的变种,使用SRCU(sleepable read-copy update)来替代rw-semaphores进行通知链的保护。
根据不同类型的通知链,内核提供了不同的注册函数,具体如下:

相应的内核还提供了事件发布函数,由通知者调用来进行事件发布并对通知链中注册的接收者操作进行调用,细节如下:

Linux内核还提供了head的定义宏,细节如下:


具体的事件是由通知者模块进行定义的,相应的接收者也需要清楚具体的事件,并在回调中根据事件进行正确的操作。对接收者来说,重要的是将block注册加入正确的通知链中,要做到这一点需要知道具体的head。这相当于要知道模块的细节,提高了模块间的耦合度,更好的方法是事件的发布者所在的模块提供一个接口函数用于注册block信息,这样接收者就不需要关心事件发布模块的细节,降低了通知链间各模块的耦合度。Linux内核也是通过该方法实现的。
内核运行过程中会有各种各样的异步事件发生,而通过通知链技术使得系统中各个模块的交互变得简洁,事件的发布者只要将事件发布即可,可以不用关心接收者的细节,接收者则只关心事件而不用关心发布者的实现,这样的设计简单、清晰并且耦合度也是很低的。
相关推荐