netlink机制-实现系统调用

晨曦之光 发布于 2012/04/10 14:57
阅读 652
收藏 0

上篇文章《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); //调用sys_read
                (*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;   //这是use_mm的地址,通过/proc/kallsyms得到
        unuse=0xc0189ca0; //这是unuse_mm的地址,通过/proc/kallsyms得到
        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机制了。
3.同步请求和异步请求如何区分也是一个问题 ,如果是同步请求,netlink将请求分发到具体模块之后,具体处理模块的工作队列要等待其完成,如果是异步请求,netlink只需要将请求分发到具体模块,不需要等待完成,而是通过sleep/wakeup的方式进行通知,然后netlink通过一个netlink_unicast来发送完成消息,此时,用户态进程可能正在poll这个netlink。
3.用户态进程如何配合。用户态调用实际上都是异步的,所谓的同步方式此时退化成了一层封装,比如同步调用等价于以下的逻辑:
sync-call() {
    sendmsg
    recvmsg
}
而异步调用则是:
async-call() {
    sendmsg
}
这一点和PCIe总线很类似的,而且和windows的机制也是很类似,Windows本身就是异步的,它就是这样实现的,Windows是基于消息传递的。
4.基于netlink的IPC。进程1封装一个syscall_info结构体
struct IPC_info {
    int target_pid;
    char msg[MAX];
}
struct syscall_info {
    int syscall_num;
    char *parameter;
    struct IPC_info iinfo;
}
然后通过netlink发往内核,内核收到并解析之后,用target_pid作为netlink_unicast的参数将msg发送出去
5.由4可知,netlink虽然很和谐,然后如果要想全面使用之的话,不得不对整个系统进行全面改造,每一个用户态进程几乎都要侦听netlink,为了IPC。
     由此可见,netlink能完成的任务很全面,包括:
1.从内核往用户态进程发送信息,比如udevd使用的sysfs的uevent
2.从用户态往内核态具体模块发信息,比如iproute2
3.用户态执行系统调用,比如上一篇的kill系统调用的netlink实现
4.用户态执行的异步系统调用,比如上述的aio-read的netlink实现
5.用户态进程间通信,比如...这个太简单了,没有实现

netlink就是操作系统中所有消息的中转和分发站,可以将netlink比作系统软件中的PCIe,那么用户态进程和内核态具体处理模块就是PCIe端点,而类似上述的read_syscall_thread内核线程就是PCIe桥或者控制器,系统中总的netlink消息分发线程就类似于PCIe中的Root complex


原文链接:http://blog.csdn.net/dog250/article/details/6427996
加载中
返回顶部
顶部