Chinaunix首页 | 论坛 | 博客
  • 博客访问: 572650
  • 博文数量: 168
  • 博客积分: 62
  • 博客等级: 民兵
  • 技术积分: 442
  • 用 户 组: 普通用户
  • 注册时间: 2011-04-30 11:45
文章分类

全部博文(168)

文章存档

2016年(2)

2015年(19)

2014年(98)

2013年(22)

2012年(6)

2011年(21)

分类: LINUX

2014-04-06 14:03:55

    最近在工作时遇到了一个问题,需要解决内核态与用户态之间的通知机制问题。

    一般来说:Linux进程间通信有五大方案:管道,消息队列,信号量,共享内存,套接字。
    管道我不是很熟,只了解一般管道局限与父子进程之间,首先就被我排除了,因为我要做的是相互独立的进程间通信,命名管道似乎不局限于父子进程,但在内核态怎么使用不清楚
    消息队列完全不了解。
    信号量的核心是一个内核变量的原子操作,但接口只体现在用户态,而且信号量的P V操作更多做的好像是互斥,而不是我想要的通知唤醒机制。
    共享内存就更麻烦了,接口只在用户态,如果自己想做内核态与用户态之间的共享内存,得自己写file,然后提供mmap接口。
    套接字之前只是用过af_inet的tcp/udp与af_unix的dgram,还是上面的那个问题,内核没有明确的接口提供,虽然可以自己去用比如sock->ops->recvmsg这样的函数去调用,但毕竟需要自己构造入参,感觉还是不太安全。
   
    那么剩下的似乎只有netlink了,这个socket明确地提供了内核的发包函数,因为它明确地export出了netlink_kernel_create函数,所以内核态的函数得以用这个sock来进行发包。但是一个是用户态需要注册收包函数,另一个内核态发包还是免不了要组装skb,对于我单纯地只想进行通知唤醒来说还是过于复杂了。

    于是我再次寻找,发现了eventfd这个神器,在KVM与Qemu的通信之间,eventfd被大牛使用的出神入化,仔细地分析了一下源码,发现这个东西就如名字所说,纯是为了通知而存在的。
    作为一个file(linux里有不是file的东西么~~),它的private_data结构体 eventfd_ctx只有可怜的四个变量。
struct eventfd_ctx {
    struct kref kref;   /* 这个就不多说了,file计数用的,用于get/put */
    wait_queue_head_t wqh; /* 这个用来存放用户态的进程wait项,有了它通知机制才成为可能 */
/*
* Every time that a write(2) is performed on an eventfd, the
* value of the __u64 being written is added to "count" and a
* wakeup is performed on "wqh". A read(2) will return the "count"
* value to userspace, and will reset "count" to zero. The kernel
* side eventfd_signal() also, adds to the "count" counter and
* issue a wakeup.
*/
    __u64 count;  /* 这个就是一个技术器,应用程序可以自己看着办,read就是取出然后清空,write就是把value加上 */
    unsigned int flags;  /* 所有的file都有的吧,用来存放阻塞/非阻塞标识或是O_CLOEXEC之类的东西 */
};
    我之所以选用它是因为它有 eventfd_signal 这个特地为内核态提供的接口,下面的是注释。
 * This function is supposed to be called by the kernel in paths that do not
 * allow sleeping. In this function we allow the counter to reach the ULLONG_MAX
 * value, and we signal this as overflow condition by returining a POLLERR  to poll(2).
    其实看代码会更清晰一些

点击(此处)折叠或打开

  1. int eventfd_signal(struct eventfd_ctx *ctx, int n)
  2. {
  3.     unsigned long flags;

  4.     if (n < 0)
  5.         return -EINVAL;
  6.     spin_lock_irqsave(&ctx->wqh.lock, flags);
  7.     if (ULLONG_MAX - ctx->count < n)
  8.         n = (int) (ULLONG_MAX - ctx->count);
  9.     ctx->count += n;
  10.     if (waitqueue_active(&ctx->wqh))
  11.         wake_up_locked_poll(&ctx->wqh, POLLIN);
  12.     spin_unlock_irqrestore(&ctx->wqh.lock, flags);

  13.     return n;
  14. }
    本质就是做一次唤醒,不用read,也不用write,与eventfd_write的区别是不用阻塞
    
    下面说一下我的具体用法:
    内核态是一个模块,注册一个misc设备,创建内核线程工作(参数为模块的file->private_data)。提供ioctl接口供用户态进程下发自己eventfd创建的fd,保存在内核线程可以访问到的file->private_data中。
    当内核态想通知用户态时,直接使用eventfd_signal,此时用户态线程需要先把自己放在eventfd_ctx->wqh上,有两种方案,一个是调用read,一个是调用poll。 如果是read,之后会将eventfd_ctx->count清零,下次还能阻塞住。但是如果使用poll,之后count并未清零,导致再次poll时,即使内核态没有eventfd_signal,poll也会即时返回。
    用户态通知内核态稍微麻烦一点,,首先需要再创建一个eventfd,然后下发给file->private_data(这里的操作同上面),额外需要在模块里做一个iotcl,专门负责用户态来通知内核态,函数里就做
eventfd_signal,内核态线程需要先放在eventfd_ctx->wqh上,可以利用vfs_read,或者自己在内核态做一次poll(似乎又麻烦了)。

    文章写到这里,好像有点写不下去了,好吧,这是因为弥补一句谎言需要额外的一千句谎言。虽然我是需要内核态线程与用户态线程通信,但是这个内核态线程其实是由另一个用户态线程创建的,因此:本质上来说还是用户态的线程间通信的问题。只不过eventfd比较简单,一个是效率高(看代码就看出来,不可能有更简单的进程间通信了),一个是在内核态提供了一个通知函数。所以满足了我的复杂需求。

    而当我想把问题简化为单纯的用户态线程与内核间通信的模型时,我才发现,原来真的是netlink才是综合来说最好的。虽然我觉得用它来做通知唤醒开销有些大。但是相对也使用简单一些。我用eventfd真的通知起来虽然精炼,但是事先的fd下发,内核模块里file的创建,ioctl的提供都是比较大的额外开销。

阅读(819) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~