Linux netlink机制-实现系统调用

《Linux的配置接口-netlink原理和设计》展示了netlink如何完成kill系统调用,然而这个例子不足以说明netlink的强大,如果我不实现一个netlink式的系统调用,很多人是不会信服的,本文就展示一个netlink实现的read调用。
在展示代码之前,我还是要不厌其烦的说一下netlink的优点,总的来说,netlink有以下三个优点:

1.netlink便于提供统一的入口,它的作用类似于socketcall。
2.netlink仅提供链路,和具体的控制逻辑无关,控制逻辑由消息格式决定

3.netlink天生就是基于消息的,而基于消息的最善于做“异步”这件事。netlink的处理可以由单独的内核线程来完成。

下面的例子展示如何使用netlink做异步IO操作:

  1. #define READ_NETLINK 28   
  2. static struct sock *netlink_readcall_sock;   
  3. static int flag = 0;   
  4. static DECLARE_COMPLETION(exit_completion);   
  5. static void receive(struct sock * sk, int length)   
  6. {   
  7.     wake_up(sk->sk_sleep);   
  8. }   
  9. void (*use)(struct mm_struct* mm);   
  10. void (*unuse)(struct mm_struct* mm);   
  11. struct readi {   
  12.     int pid;   
  13.     int fd;   
  14.     char *ptr;   
  15.     int count;   
  16. };   
  17. static int read_syscall_thread(void * pData)   
  18. {   
  19.         struct sk_buff * skb = NULL;   
  20.         struct nlmsghdr * nlhdr = NULL;   
  21.         struct readi sigi;   
  22.         struct fs_struct *fs;   
  23.     struct files_struct *files;   
  24.         DEFINE_WAIT(wait);   
  25.         daemonize("read call");   
  26.         while (flag == 0) {   
  27.             prepare_to_wait(netlink_readcall_sock->sk_sleep, &wait, TASK_INTERRUPTIBLE);   
  28.             schedule();   
  29.             finish_wait(netlink_readcall_sock->sk_sleep, &wait);   
  30.             while ((skb = skb_dequeue(&netlink_readcall_sock->sk_receive_queue)) != NULL) {   
  31.                 struct task_struct *tsk;   
  32.                     int pid;   
  33.                     nlhdr = (struct nlmsghdr *)skb->data;   
  34.                     pid = nlhdr->nlmsg_pid;   
  35.             memset(&sigi, 0, sizeof(sigi));   
  36.             memcpy(&sigi, NLMSG_DATA(nlhdr), sizeof(sigi));   
  37.             tsk = find_task_by_pid(sigi.pid);   
  38.                 fs = current->fs;   
  39.                 files = current->files;   
  40.                 current->fs = tsk->fs;   
  41.                 current->files = tsk->files;   
  42.                 (*use)(tsk->mm);    //切换到调用进程的空间   
  43.                 sys_read(sigi.fd, sigi.ptr, sigi.count); //调用sys_read   
  44.                 (*unuse)(tsk->mm);    //恢复地址空间   
  45.                 current->fs = fs;   
  46.                 current->files = files;   
  47.                    netlink_unicast(netlink_readcall_sock, skb, pid, MSG_DONTWAIT); //通知消息   
  48. 的发送者调用完成。   
  49.             }   
  50.         }   
  51.         complete(&exit_completion);   
  52.         return 0;   
  53. }   
  54. int init_module()   
  55. {   
  56.     use=0xc0189ae0;   //这是use_mm的地址,通过/proc/kallsyms得到   
  57.         unuse=0xc0189ca0; //这是unuse_mm的地址,通过/proc/kallsyms得到   
  58.         netlink_readcall_sock = netlink_kernel_create(READ_NETLINK, receive);   
  59.         kernel_thread(read_syscall_thread, NULL, CLONE_KERNEL);   
  60.         return 0;   
  61. }   
  62. void cleanup_module()   
  63. {   
  64.     flag = 1;   
  65.     wake_up(netlink_readcall_sock->sk_sleep);   
  66.     wait_for_completion(&exit_completion);   
  67.     sock_release(netlink_readcall_sock->sk_socket);   
  68. }   
  69. MODULE_LICENSE("GPL");   
用户态进程部分代码:
  1. sigi.pid=getpid();   
  2. sigi.fd = open("/root/netlink/device/aaa", O_RDWR);   
  3. sigi.ptr = buff;   
  4. sigi.count=4;   
  5. sd = socket(AF_NETLINK, SOCK_RAW, 28);   
  6. ...   
  7. bind(sd, (struct sockaddr*)&saddr, sizeof(saddr));   
  8. ...   
  9. ret = sendmsg(sd, &msg, 0);  
经过测试,完全可以使用netlink来读取文件。在以上的例子中,编写过程中遇到了一个困难,那就是use_mm和unuse_mm并不是内核的导出函数,如果想使用那就得重新编译内核,可是我不想那么做,于是只有使用一种不规范的方式,那就是想办法取到use_mm和unuse_mm函数的地址,这个恰好可以从procfs得到(要是windows,恐怕又可以在看雪论坛上长篇大论了),这个问题很简单的解决了。我的意思是说,有的时候,80/20原则和墨菲法则 处处起作用,我们应该发挥主观能动性解决主要问题而不是被旁的问题引入歧途!比如,我们不应该想办法使内核导出use_mm函数,而是想办法使用它即可!
     另外要说的是,本例子中仅仅实现了read系统调用,诸如getpid以及open调用仍然使用标准系统调用,然而这并不是说这些一定要使用标准系统调用,鉴于本文篇幅有限,我只能列举出一个典型的系统调用-read的netlink实现,其它的系统调用用类似的方式也能实现!值得注意的是,用netlink实现read的方式中,重要的不是netlink本身,而是use_mm的调用将地址空间切换到调用进程,以便使用ptr指针,还有files的切换,以便使用fd文件描述符...
     这个例子仅仅是预研性质的,证明了可行性,还有很多地方有待改进。大致上有以下几类问题:
1.read_syscall_thread这个内核线程仅仅处理了read这个系统调用,实际上应该使它成为一个总体的分发者,分发所有的系统调用,然后将具体的工作交给工作队列 -workqueue。该内核线程中可以配置一个链表,保存所有的接收到的系统调用请求,可以来自于不同进程,然后将这些请求分发到工作队列,这样单独的请求不会阻塞整个处理流程。
2.数据结构readi应该设计成syscall_info:
struct syscall_info {
    int syscall_num;
    char *parameter;
    ...
}
这样所有的syscall就都可以用netlink机制了。

相关推荐