《Linux的配置接口-netlink原理和设计》展示了netlink如何完成kill系统调用,然而这个例子不足以说明netlink的强大,如果我不实现一个netlink式的系统调用,很多人是不会信服的,本文就展示一个netlink实现的read调用。
在展示代码之前,我还是要不厌其烦的说一下netlink的优点,总的来说,netlink有以下三个优点:
1.netlink便于提供统一的入口,它的作用类似于socketcall。
2.netlink仅提供链路,和具体的控制逻辑无关,控制逻辑由消息格式决定
3.netlink天生就是基于消息的,而基于消息的最善于做“异步”这件事。netlink的处理可以由单独的内核线程来完成。
下面的例子展示如何使用netlink做异步IO操作:
- #define READ_NETLINK 28
- static struct sock *netlink_readcall_sock;
- static int flag = 0;
- static DECLARE_COMPLETION(exit_completion);
- static void receive(struct sock * sk, int length)
- {
- wake_up(sk->sk_sleep);
- }
- void (*use)(struct mm_struct* mm);
- void (*unuse)(struct mm_struct* mm);
- struct readi {
- int pid;
- int fd;
- char *ptr;
- int count;
- };
- static int read_syscall_thread(void * pData)
- {
- struct sk_buff * skb = NULL;
- struct nlmsghdr * nlhdr = NULL;
- struct readi sigi;
- struct fs_struct *fs;
- struct files_struct *files;
- DEFINE_WAIT(wait);
- daemonize("read call");
- while (flag == 0) {
- prepare_to_wait(netlink_readcall_sock->sk_sleep, &wait, TASK_INTERRUPTIBLE);
- schedule();
- finish_wait(netlink_readcall_sock->sk_sleep, &wait);
- while ((skb = skb_dequeue(&netlink_readcall_sock->sk_receive_queue)) != NULL) {
- struct task_struct *tsk;
- int pid;
- nlhdr = (struct nlmsghdr *)skb->data;
- pid = nlhdr->nlmsg_pid;
- memset(&sigi, 0, sizeof(sigi));
- memcpy(&sigi, NLMSG_DATA(nlhdr), sizeof(sigi));
- tsk = find_task_by_pid(sigi.pid);
- fs = current->fs;
- files = current->files;
- current->fs = tsk->fs;
- current->files = tsk->files;
- (*use)(tsk->mm);
- sys_read(sigi.fd, sigi.ptr, sigi.count);
- (*unuse)(tsk->mm);
- current->fs = fs;
- current->files = files;
- netlink_unicast(netlink_readcall_sock, skb, pid, MSG_DONTWAIT);
- 的发送者调用完成。
- }
- }
- complete(&exit_completion);
- return 0;
- }
- int init_module()
- {
- use=0xc0189ae0;
- unuse=0xc0189ca0;
- netlink_readcall_sock = netlink_kernel_create(READ_NETLINK, receive);
- kernel_thread(read_syscall_thread, NULL, CLONE_KERNEL);
- return 0;
- }
- void cleanup_module()
- {
- flag = 1;
- wake_up(netlink_readcall_sock->sk_sleep);
- wait_for_completion(&exit_completion);
- sock_release(netlink_readcall_sock->sk_socket);
- }
- MODULE_LICENSE("GPL");
用户态进程部分代码:
- sigi.pid=getpid();
- sigi.fd = open("/root/netlink/device/aaa", O_RDWR);
- sigi.ptr = buff;
- sigi.count=4;
- sd = socket(AF_NETLINK, SOCK_RAW, 28);
- ...
- bind(sd, (struct sockaddr*)&saddr, sizeof(saddr));
- ...
- 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机制了。