精华内容
下载资源
问答
  • linux用户空间内核空间通信——Netlink通信机制

    万次阅读 多人点赞 2018-07-06 17:19:42
    但是注意虽然Netlink主要用于用户空间和内核空间通信,但是也能用于用户空间的两个进程通信。只是进程间通信有其他很多方式,一般不用Netlink。除非需要用到Netlink的广播特性时。那么Netlink有什么优势呢?...

    一:什么是Netlink通信机制

    Netlink是linux提供的用于内核和用户态进程之间的通信方式。

    但是注意虽然Netlink主要用于用户空间和内核空间的通信,但是也能用于用户空间的两个进程通信。只是进程间通信有其他很多

    方式,一般不用Netlink。除非需要用到Netlink的广播特性时。

    那么Netlink有什么优势呢?

    一般来说用户空间和内核空间的通信方式有三种:/proc、ioctl、Netlink。而前两种都是单向的,但是Netlink可以实现双工通信。

    Netlink协议基于BSD socket和AF_NETLINK地址簇(address family),使用32位的端口号寻址(以前称作PID),每个Netlink协议

    (或称作总线,man手册中则称之为netlink family),通常与一个或一组内核服务/组件相关联,如NETLINK_ROUTE用于获取和

    设置路由与链路信息、NETLINK_KOBJECT_UEVENT用于内核向用户空间的udev进程发送通知等。

    netlink具有以下特点:

    ① 支持全双工、异步通信(当然同步也支持)

    ② 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)

    ③ 在内核空间使用专用的内核API接口

    ④ 支持多播(因此支持“总线”式通信,可实现消息订阅)

    ⑤ 在内核端可用于进程上下文与中断上下文

    二:用户态数据结构

    首先看一下几个重要的数据结构的关系:


    1.struct msghdr

    msghdr这个结构在socket变成中就会用到,并不算Netlink专有的,这里不在过多说明。只说明一下如何更好理解这个结构的功能。我们

    知道socket消息的发送和接收函数一般有这几对:recv/send、readv/writev、recvfrom/sendto。当然还有recvmsg/sendmsg,

    前面三对函数各有各的特点功能,而recvmsg/sendmsg就是要囊括前面三对的所有功能,当然还有自己特殊的用途。msghdr的前两个

    成员就是为了满足recvfrom/sendto的功能,中间两个成员msg_iov和msg_iovlen则是为了满足readv/writev的功能,而最后的

    msg_flags则是为了满足recv/send中flag的功能,剩下的msg_control和msg_controllen则是满足recvmsg/sendmsg特有的功能。

    2.struct sockaddr_ln

    struct sockaddr_ln为Netlink的地址,和我们通常socket编程中的sockaddr_in作用一样,他们的结构对比如下:


    struct sockaddr_nl的详细定义和描述如下:

    struct sockaddr_nl
    {
        sa_family_t nl_family; /*该字段总是为AF_NETLINK */
        unsigned short nl_pad; /* 目前未用到,填充为0*/
        __u32 nl_pid; /* process pid */
        __u32 nl_groups; /* multicast groups mask */
    };

    (1) nl_pid:在Netlink规范里,PID全称是Port-ID(32bits),其主要作用是用于唯一的标识一个基于netlink的socket通道。通常

    情况下nl_pid都设置为当前进程的进程号。前面我们也说过,Netlink不仅可以实现用户-内核空间的通信还可使现实用户空间两

    个进程之间,或内核空间两个进程之间的通信。该属性为0时一般指内核。

    (2) nl_groups:如果用户空间的进程希望加入某个多播组,则必须执行bind()系统调用。该字段指明了调用者希望加入的多播组

    号的掩码(注意不是组号,后面我们会详细讲解这个字段)。如果该字段为0则表示调用者不希望加入任何多播组。对于每个隶属于

    Netlink协议域的协议,最多可支持32个多播组(因为nl_groups的长度为32比特),每个多播组用一个比特来表示。

    3.struct nlmsghdr

    Netlink的报文由消息头和消息体构成,struct nlmsghdr即为消息头。消息头定义在文件里,由结构体nlmsghdr表示:

    struct nlmsghdr
    {
        __u32 nlmsg_len; /* Length of message including header */
        __u16 nlmsg_type; /* Message content */
        __u16 nlmsg_flags; /* Additional flags */
        __u32 nlmsg_seq; /* Sequence number */
        __u32 nlmsg_pid; /* Sending process PID */
    };

    消息头中各成员属性的解释及说明:

    (1) nlmsg_len:整个消息的长度,按字节计算。包括了Netlink消息头本身。

    (2) nlmsg_type:消息的类型,即是数据还是控制消息。目前(内核版本2.6.21)Netlink仅支持四种类型的控制消息,如下:

    a) NLMSG_NOOP-空消息,什么也不做;

    b) NLMSG_ERROR-指明该消息中包含一个错误;

    c) NLMSG_DONE-如果内核通过Netlink队列返回了多个消息,那么队列的最后一条消息的类型为NLMSG_DONE,其余所有消息的nlmsg_flags属性都被设置NLM_F_MULTI位有效。

    d) NLMSG_OVERRUN-暂时没用到。

    (3) nlmsg_flags:附加在消息上的额外说明信息,如上面提到的NLM_F_MULTI。

    三:用户空间Netlink socket API

    1.创建socket

    int socket(int domain, int type, int protocol)

    domain指代地址族,即AF_NETLINK;

    套接字类型为SOCK_RAW或SOCK_DGRAM,因为netlink是一个面向数据报的服务;

    protocol选择该套接字使用哪种netlink特征。

    以下是几种预定义的协议类型:

    NETLINK_ROUTE,

    NETLINK_FIREWALL,

    NETLINK_APRD,

    NETLINK_ROUTE6_FW。

    可以非常容易的添加自己的netlink协议。

    为每一个协议类型最多可以定义32个多播组。

    每一个多播组用一个bitmask来表示,1<<i(0<=i<= 31),这在一组进程和内核进程协同完成一项任务时非常有用。发送多播

    netlink消息可以减少系统调用的数量,同时减少用来维护多播组成员信息的负担。

    2.地址绑定bind()

    bind(fd, (struct sockaddr*)&, nladdr, sizeof(nladdr));

    3.发送netlink消息

    为了发送一条netlink消息到内核或者其他的用户空间进程,另外一个struct sockaddr_nl nladdr需要作为目的地址,这和使用

    sendmsg()发送一个UDP包是一样的。

    如果该消息是发送至内核的,那么nl_pid和nl_groups都置为0.

    如果消息是发送给另一个进程的单播消息,nl_pid是另外一个进程的pid值而nl_groups为零。

    如果消息是发送给一个或多个多播组的多播消息,所有的目的多播组必须bitmask必须or起来从而形成nl_groups域。

    sendmsg(fd, &, msg, 0);

    4.接收netlink消息

    一个接收程序必须分配一个足够大的内存用于保存netlink消息头和消息负载。然后其填充struct msghdr msg,再使用标准的

    recvmsg()函数来接收netlink消息。

    当消息被正确的接收之后,nlh应该指向刚刚接收到的netlink消息的头。nladdr应该包含接收消息的目的地址,其中包括了消息发送

    者的pid和多播组。同时,宏NLMSG_DATA(nlh),定义在netlink.h中,返回一个指向netlink消息负载的指针。调用close(fd)关闭fd

    描述符所标识的socket。

    recvmsg(fd, &, msg, 0);

    四:内核空间Netlink socket API

    1.创建 netlink socket

    struct sock *netlink_kernel_create(struct net *net,
                                       int unit,unsigned int groups,
                                       void (*input)(struct sk_buff *skb),
                                       struct mutex *cb_mutex,struct module *module);

    参数说明:

    (1) net:是一个网络名字空间namespace,在不同的名字空间里面可以有自己的转发信息库,有自己的一套net_device等等。

    默认情况下都是使用 init_net这个全局变量。

    (2) unit:表示netlink协议类型,如NETLINK_TEST、NETLINK_SELINUX。

    (3) groups:多播地址。

    (4) input:为内核模块定义的netlink消息处理函数,当有消 息到达这个netlink socket时,该input函数指针就会被引用,且只

    有此函数返回时,调用者的sendmsg才能返回。

    (5)  cb_mutex:为访问数据时的互斥信号量。

    (6) module: 一般为THIS_MODULE。

    2.发送单播消息 netlink_unicast

    int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)

    参数说明:

    (1) ssk:为函数 netlink_kernel_create()返回的socket。

    (2) skb:存放消息,它的data字段指向要发送的netlink消息结构,而 skb的控制块保存了消息的地址信息,宏

    NETLINK_CB(skb)就用于方便设置该控制块。

    (3) pid:为接收此消息进程的pid,即目标地址,如果目标为组或内核,它设置为 0。

    (4) nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回;而如果为0,该函数在没有接

    收缓存可利用定时睡眠。

    3.发送广播消息 netlink_broadcast

    int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)
    

    前面的三个参数与 netlink_unicast相同,参数group为接收消息的多播组,该参数的每一个位代表一个多播组,因此如果发送给

    多个多播组,就把该参数设置为多个多播组组ID的位或。参数allocation为内核内存分配类型,一般地为GFP_ATOMIC或

    GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文。

    4.释放 netlink socket

    int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)

    五:用户态范例一

    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <string.h>
    #include <asm/types.h>
    #include <linux/netlink.h>
    #include <linux/socket.h>
    #include <errno.h>
    #define MAX_PAYLOAD 1024 // maximum payload size
    #define NETLINK_TEST 25 //自定义的协议
    int main(int argc, char* argv[])
    {
        int state;
        struct sockaddr_nl src_addr, dest_addr;
        struct nlmsghdr *nlh = NULL; //Netlink数据包头
        struct iovec iov;
        struct msghdr msg;
        int sock_fd, retval;
        int state_smg = 0;
        // Create a socket
        sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
        if(sock_fd == -1){
            printf("error getting socket: %s", strerror(errno));
            return -1;
        }
        // To prepare binding
        memset(&src_addr, 0, sizeof(src_addr));
        src_addr.nl_family = AF_NETLINK;
        src_addr.nl_pid = 100; //A:设置源端端口号
        src_addr.nl_groups = 0;
        //Bind
        retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
        if(retval < 0){
            printf("bind failed: %s", strerror(errno));
            close(sock_fd);
            return -1;
        }
        // To orepare create mssage
        nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
        if(!nlh){
            printf("malloc nlmsghdr error!\n");
            close(sock_fd);
            return -1;
    }
        memset(&dest_addr,0,sizeof(dest_addr));
        dest_addr.nl_family = AF_NETLINK;
        dest_addr.nl_pid = 0; //B:设置目的端口号
        dest_addr.nl_groups = 0;
        nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
        nlh->nlmsg_pid = 100; //C:设置源端口
        nlh->nlmsg_flags = 0;
        strcpy(NLMSG_DATA(nlh),"Hello you!"); //设置消息体
        iov.iov_base = (void *)nlh;
        iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
        //Create mssage
        memset(&msg, 0, sizeof(msg));
        msg.msg_name = (void *)&dest_addr;
        msg.msg_namelen = sizeof(dest_addr);
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        //send message
        printf("state_smg\n");
        state_smg = sendmsg(sock_fd,&msg,0);
        if(state_smg == -1)
        {
            printf("get error sendmsg = %s\n",strerror(errno));
        }
        memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));
        //receive message
        printf("waiting received!\n");
        while(1){
            printf("In while recvmsg\n");
            state = recvmsg(sock_fd, &msg, 0);
            if(state<0)
            {
                printf("state<1");
            }
            printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));
        }
        close(sock_fd);
        return 0;
    }

    上面程序首先向内核发送一条消息;“Hello you”,然后进入循环一直等待读取内核的回复,并将收到的回复打印出来。如果

    看上面程序感觉很吃力,那么应该首先复习一下UDP中使用sendmsg的用法,特别时struct msghdr的结构要清楚,这里再赘

    述。下面主要分析与UDP发送数据包的不同点:

    1. socket地址结构不同,UDP为sockaddr_in,Netlink为struct sockaddr_nl;

    2. 与UDP发送数据相比,Netlink多了一个消息头结构struct nlmsghdr需要我们构造。

    注意代码注释中的A、B、C三处分别设置了pid。首先解释一下什么是pid,网上很多文章把这个字段说成是进程的pid,其实这

    完全是望文生义。这里的pid和进程pid没有什么关系,仅仅相当于UDP的port。对于UDP来说port和ip标示一个地址,那对我们

    的NETLINK_TEST协议(注意Netlink本身不是一个协议)来说,pid就唯一标示了一个地址。所以你如果用进程pid做为标示当然

    也是可以的。当然同样的pid对于NETLINK_TEST协议和内核定义的其他使用Netlink的协议是不冲突的(就像TCP的80端口和

    UDP的80端口)。

    下面分析这三处设置pid分别有什么作用,首先A和B位置的比较好理解,这是在地址(sockaddr_nl)上进行的设置,就是相当

    于设置源地址和目的地址(其实是端口),只是注意B处设置pid为0,0就代表是内核,可以理解为内核专用的pid,那么用户进

    程就不能用0做为自己的pid吗?这个只能说如果你非要用也是可以的,只是会产生一些问题,后面在分析。接下来看为什么C处

    的消息头仍然需要设置pid呢?这里首先要知道一个前提:内核不会像UDP一样根据我们设置的原、目的地址为我们构造消息

    头,所以我们不在包头写入我们自己的地址(pid),那内核怎么知道是谁发来的报文呢?当然如果内核只是处理消息不需要回

    复进程的话舍不设置这个消息头pid都可以。

    所以每个pid的设置功能不同:A处的设置是要设置发送者的源地址,有人会说既然源地址又不会自动填充到报文中,我们为什么

    还要设置这个,因为你还可能要接收回复啊。就像寄信,你连“门牌号”都没有,即使你在写信时候写上你的地址是100号,对

    方回信目的地址也是100号,但是邮局发现根本没有这个地址怎么可能把信送到你手里呢?所以A的主要作用是注册源地址,保证

    可以收到回复,如果不需要回复当然可以简单将pid设置为0;B处自然就是收信人的地址,pid为0代表内核的地址,假如有一个

    进程在101号上注册了地址,并调用了recvmsg,如果你将B处的pid设置为101,那数据包就发给了另一个进程,这就实现了使

    用Netlink进行进程间通信;C相当于你在信封上写的源地址,通常情况下这个应该和你的真实地址(A)处注册的源地址相同,

    当然你要是不想收到回信,又想恶搞一下或者有特殊需求,你可以写成其他进程注册的pid(比如101)。这和我们现实中寄信是

    一样的,你给你朋友写封情书,把写信人写成你的另一个好基友,然后后果你懂得……

    好了,有了这个例子我们就大概知道用户态怎么使用Netlink了。

    六:内核态程序范例一

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/timer.h>
    #include <linux/time.h>
    #include <linux/types.h>
    #include <net/sock.h>
    #include <net/netlink.h>
    #define NETLINK_TEST 25
    #define MAX_MSGSIZE 1024
    int stringlength(char *s);
    int err;
    struct sock *nl_sk = NULL;
    int flag = 0;
    //向用户态进程回发消息
    void sendnlmsg(char *message, int pid)
    {
        struct sk_buff *skb_1;
        struct nlmsghdr *nlh;
        int len = NLMSG_SPACE(MAX_MSGSIZE);
        int slen = 0;
        if(!message || !nl_sk)
        {
            return ;
        }
        printk(KERN_ERR "pid:%d\n",pid);
        skb_1 = alloc_skb(len,GFP_KERNEL);
        if(!skb_1)
        {
            printk(KERN_ERR "my_net_link:alloc_skb error\n");
        }
        slen = stringlength(message);
        nlh = nlmsg_put(skb_1,0,0,0,MAX_MSGSIZE,0);
        NETLINK_CB(skb_1).pid = 0;
        NETLINK_CB(skb_1).dst_group = 0;
        message[slen]= '\0';
        memcpy(NLMSG_DATA(nlh),message,slen+1);
        printk("my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh));
        netlink_unicast(nl_sk,skb_1,pid,MSG_DONTWAIT);
    }
    int stringlength(char *s)
    {
        int slen = 0;
        for(; *s; s++)
        {
            slen++;
        }
        return slen;
    }
    //接收用户态发来的消息
    void nl_data_ready(struct sk_buff *__skb)
     {
         struct sk_buff *skb;
         struct nlmsghdr *nlh;
         char str[100];
         struct completion cmpl;
         printk("begin data_ready\n");
         int i=10;
         int pid;
         skb = skb_get (__skb);
         if(skb->len >= NLMSG_SPACE(0))
         {
             nlh = nlmsg_hdr(skb);
             memcpy(str, NLMSG_DATA(nlh), sizeof(str));
             printk("Message received:%s\n",str) ;
             pid = nlh->nlmsg_pid;
             while(i--)
            {//我们使用completion做延时,每3秒钟向用户态回发一个消息
                init_completion(&cmpl);
                wait_for_completion_timeout(&cmpl,3 * HZ);
                sendnlmsg("I am from kernel!",pid);
            }
             flag = 1;
             kfree_skb(skb);
        }
     }
    // Initialize netlink
    int netlink_init(void)
    {
        nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 1,
                                     nl_data_ready, NULL, THIS_MODULE);
        if(!nl_sk){
            printk(KERN_ERR "my_net_link: create netlink socket error.\n");
            return 1;
        }
        printk("my_net_link_4: create netlink socket ok.\n");
        return 0;
    }
    static void netlink_exit(void)
    {
        if(nl_sk != NULL){
            sock_release(nl_sk->sk_socket);
        }
        printk("my_net_link: self module exited\n");
    }
    module_init(netlink_init);
    module_exit(netlink_exit);
    MODULE_AUTHOR("zhao_h");
    MODULE_LICENSE("GPL");

    附上内核代码的Makefile文件:

    ifneq ($(KERNELRELEASE),)
    obj-m :=netl.o
    else
    KERNELDIR ?=/lib/modules/$(shell uname -r)/build
    PWD :=$(shell pwd)
    default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    endif

    我们将内核模块insmod后,运行用户态程序,结果如下:


    这个结果复合我们的预期,但是运行过程中打印出“state_smg”卡了好久才输出了后面的结果。这时候查看客户进程是处于D

    状态的(不了解D状态的同学可以google一下)。这是为什么呢?因为进程使用Netlink向内核发数据是同步,内核向进程发数

    据是异步。什么意思呢?也就是用户进程调用sendmsg发送消息后,内核会调用相应的接收函数,但是一定到这个接收函数执行

    完用户态的sendmsg才能够返回。我们在内核态的接收函数中调用了10次回发函数,每次都等待3秒钟,所以内核接收函数30秒

    后才返回,所以我们用户态程序的sendmsg也要等30秒后才返回。相反,内核回发的数据不用等待用户程序接收,这是因为内核

    所发的数据会暂时存放在一个队列中。

    再来回到之前的一个问题,用户态程序的源地址(pid)可以用0吗?我把上面的用户程序的A和C处pid都改为了0,结果一运行

    就死机了。为什么呢?我们看一下内核代码的逻辑,收到用户消息后,根据消息中的pid发送回去,而pid为0,内核并不认为这

    是用户程序,认为是自身,所有又将回发的10个消息发给了自己(内核),这样就陷入了一个死循环,而用户态这时候进程一直

    处于D。

    另外一个问题,如果同时启动两个用户进程会是什么情况?答案是再调用bind时出错:“Address already in use”,这个同

    UDP一样,同一个地址同一个port如果没有设置SO_REUSEADDR两次bind就会出错,之后我用同样的方式再Netlink的socket

    上设置了SO_REUSEADDR,但是并没有什么效果。

    七:用户态范例二

    之前我们说过UDP可以使用sendmsg/recvmsg也可以使用sendto/recvfrom,那么Netlink同样也可以使用sendto/

    recvfrom。具体实现如下:

    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <string.h>
    #include <asm/types.h>
    #include <linux/netlink.h>
    #include <linux/socket.h>
    #include <errno.h>
    #define MAX_PAYLOAD 1024 // maximum payload size
    #define NETLINK_TEST 25
    int main(int argc, char* argv[])
    {
        struct sockaddr_nl src_addr, dest_addr;
        struct nlmsghdr *nlh = NULL;
        int sock_fd, retval;
        int state,state_smg = 0;
        // Create a socket
        sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
        if(sock_fd == -1){
            printf("error getting socket: %s", strerror(errno));
            return -1;
        }
        // To prepare binding
        memset(&src_addr, 0, sizeof(src_addr));
        src_addr.nl_family = AF_NETLINK;
        src_addr.nl_pid = 100;
        src_addr.nl_groups = 0;
        //Bind
        retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
        if(retval < 0){
            printf("bind failed: %s", strerror(errno));
            close(sock_fd);
            return -1;
        }
        // To orepare create mssage head
        nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
        if(!nlh){
            printf("malloc nlmsghdr error!\n");
            close(sock_fd);
            return -1;
        }
        memset(&dest_addr,0,sizeof(dest_addr));
        dest_addr.nl_family = AF_NETLINK;
        dest_addr.nl_pid = 0;
        dest_addr.nl_groups = 0;
        nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
        nlh->nlmsg_pid = 100;
        nlh->nlmsg_flags = 0;
        strcpy(NLMSG_DATA(nlh),"Hello you!");
        //send message
        printf("state_smg\n");
        sendto(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,(struct sockaddr*)(&dest_addr),sizeof(dest_addr));
        if(state_smg == -1)
        {
            printf("get error sendmsg = %s\n",strerror(errno));
        }
        memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));
        //receive message
        printf("waiting received!\n");
    while(1){
            printf("In while recvmsg\n");
    state=recvfrom(sock_fd,nlh,NLMSG_LENGTH(MAX_PAYLOAD),0,NULL,NULL);
            if(state<0)
            {
                printf("state<1");
            }
            printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));
            memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));
        }
        close(sock_fd);
        return 0;
    }

    熟悉UDP编程的同学看到这个程序一定很熟悉,除了多了一个Netlink消息头的设置。但是我们发现程序中调用了bind函数,这

    个函数再UDP编程中的客户端不是必须的,因为我们不需要把UDP socket与某个地址关联,同时再发送UDP数据包时内核会为

    我们分配一个随即的端口。但是对于Netlink必须要有这一步bind,因为Netlink内核可不会为我们分配一个pid。再强调一遍消

    息头(nlmsghdr)中的pid是告诉内核接收端要回复的地址,但是这个地址存不存在内核并不关心,这个地址只有用户端调用了

    bind后才存在。

    我们看到这两个例子都是用户态首先发起的,那Netlink是否支持内核态主动发起的情况呢?

    当然是可以的,只是内核一般需要事件触发,这里,只要和用户态约定号一个地址(pid),内核直接调用netlink_unicast就可以了。


    展开全文
  • 实用文案 Linux 内核空间用户空间通信机制的研究 Linux kernel space and user space communication mechanism 摘 要 标准文档 实用文案 Linux 是一个源码开放的操作系统 无论是普通用户还是企业用户都可以编写...
  • Linux用户和内核通信方法--netlink

    千次阅读 2019-06-18 10:38:00
    Linux用户空间和内核空间通信方法:系统调用、procfs、ioctl接口、netlink netlink 是 Linux 用户态与内核态通信最常用的一种方式。 netlink:netlink socekt是一种用于在内核态和用户态进程之间进行数据传输的...

    Linux用户空间和内核空间通信方法:系统调用、procfs、ioctl接口、netlink

     

    netlink 是 Linux 用户态与内核态通信最常用的一种方式。

    netlink:netlink socekt是一种用于在内核态和用户态进程之间进行数据传输的特殊的IPC。它通过为内核模块提供一组特殊的API,并为用户程序提供了一组标准的socket 接口的方式,实现了一种全双工的通讯连接。类似于TCP/IP中使用AF_INET地址族一样,netlink socket使用地址族AF_NETLINK。

    为新的特性添加一个新的系统调用,ioctls或者一个proc文件的做法并不是很容易的一件事情,因为我们要冒着污染内核代码并且可能破坏系统稳定性的风险去完成这件事情。netlink socket却是如此的简单,你只需要在文件netlink.h中添加一个常量来标识你的协议类型,然后,内核模块和用户程序就可以立刻使用socket风格的API进行通讯了!

    Netlink提供了一种异步通讯方式,系统调用和ioctl是同步的。因此,当我们使用一个系统调用来从用户态传递消息到内核时,如果处理这个消息的时间很长的话,内核调度的粒度就会受到影响。

    Netlink优于系统调用,ioctls和proc文件系统的另外一个特点就是它支持多点传送。一个进程可以把消息传输给一个netlink组地址,然后任意多个进程都可以监听那个组地址(并且接收消息)。这种机制为内核到用户态的事件分发提供了一种近乎完美的解决方案。

    系统调用和ioctl都属于单工方式的IPC,也就是说,这种IPC会话的发起者只能是用户态程序。Netlink 通过允许内核初始化会话的方式完美的解决了此问题,我们称之为netlink socket的双工特性

    使用 netlink 的内核部分能采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,他无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。

    在内核空间使用专用的内核API接口,在内核端可用于进程上下文与中断上下文。用户空间可使用标准的BSD socket接口。易用。

    不仅可以实现内核-用户空间通信还可以用于两个用户进程的通信,但是很少用。用户空间通信有很多方式。

     

    我需要一种可以在内核态主动发起消息的通知方式,而用户态的程序最好可以采用一种“阻塞调用”的方式等待消息。这样的模型可以最大限度的节省CPU的调度,同时可以满足及时处理的要求,最终选择了netlink完成通信的过程。

    因为netlink是一个面向数据报的服务。类似于UDP。

     

    Netlink的通信模型和socket通信非常相似,主要要点如下:

    • netlink采用自己独立的地址编码,struct sockaddr_nl;
    • 每个通过netlink发出的消息都必须附带一个netlink自己的消息头,struct nlmsghdr;
    • 用户态的netlink操作完成采用标准socket函数,非常方便和简单,有TCP/UDP socket编程基础的非常容易上手。内核态有专门的的API来使用netlink。

    参考:https://blog.csdn.net/ganshuyu/article/details/30241313/

    展开全文
  • linux内核空间和用户空间通信

    千次阅读 2012-12-21 10:45:09
    因网上已有很多介绍各种通信方式的示例代码,所以在本文中只是给出各种内核空间和用户空间通信方式的介绍说明。希望给像我一样的初学者提供一定的指导。因水平有限,欢迎各位批评指点。   1 概述 Linux内核将这...
     因网上已有很多介绍各种通信方式的示例代码,所以在本文中只是给出各种内核空间和用户空间通信方式的介绍说明。希望给像我一样的初学者提供一定的指导。因水平有限,欢迎各位批评指点。
    

     

    1         概述

    Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间“)。除了进程之间的通信外,在嵌入式设计中还经常需要进行内核空间和用户空间的信息交互。本文主要讨论内核空间和用户空间信息交互的方法。

    1.1 处理器状态

    处理器总处于以下状态中的一种:

    A、内核态,运行于进程上下文,内核代表进程运行于内核空间;

    B、内核态,运行于中断上下文,包括硬中断和软中断;

    C、用户态,运行于用户空间。

     

    1.2 不同状态的限制

    根据上面的状态分类,内核空间和用户空间之间的信息交互就分为两类,即中断上下文内核态空间与进程空间信息交互;进程上下文内核态空间和进程空间信息交互。

    内核态环境

    进入内核态的方式

    局限性

    说明

    进程上下文

    在进程中通过系统调用进入内核态,内核态代码与该进程相关。

    内核空间和进程空间的虚拟地址不同,不能直接传递信息。

    该进程的页表基地址依然在页表基地址寄存器(如X86中的CR3)中,内核空间中可以使用__user 强制使用用户空间的地址,从而进行数据交互。

    中断上下文

    硬件触发中断,或内核中挂接软中断。不与特定的进程相关。

    内核空间和进程空间的虚拟地址不同,不能直接传递信息。

    中断中不能睡眠,不能运行引起阻塞的函数。

    由于中断触发的随机性,中断上下文内核态不与特定的进程相关。

     

     

    2 各种通信方式

    本节说明各种通信方式是否适合内核空间和用户空间信息交互,以及如何使用。

     

    2.1 信号

    在进程中使用函数signal()或sigaction()安装信号时指定了关联的函数。在内核空间相进程发送信号,从内核空间返回进程空间时检查并执行相应的关联函数。

    在进程中可以使用pause()函数进入睡眠,在有信号产生并执行了相应的关联函数后进程被唤醒,继续执行。可以使用这种方式实现内核空间和用户空间的同步。

    pause()会使当前进程挂起,直到捕捉到一个信号,对指定为忽略的信号,pause()不会返回。只有执行了一个信号处理函数,并从其返回,puase()才返回-1,并将errno设为EINTR。

          

     

    2.2 信号量

           虽然原理一样,但内核空间和用户空间的信号量是完全两套系统,所以信号量不能用于内核空间和用户空间信息交互。

     

    2.3 无名管道

           无名管道只适用于有关系的进程之间通信。不能用于内核空间和用户空间信息交互。

     

    2.4 get_user()/put_user()

    get_user(x, ptr):本函数是在内核中被调用,获取用户空间指定地址的数值(一个字节或字)并保存到内核变量x中,ptr为用户空间的地址。用法举例如下:get_user(val, (int __user *)arg)

    put_user(x, ptr):在内核中被调用,将内核空间的变量x的数值(一个字节或字)保存到用户空间指定地址处,prt为用户空间地址。用法举例如下:put_user(val, (int __user *)arg)

    注明:函数用于进程上下文内核态空间,即通常在系统调用函数中使用该函数,如设备驱动中ioctl函数中。

     

     

    2.5 copy_from_user()/copy_to_user()

    主要应用于设备驱动中读写函数中,通过系统调用触发,在当前进程上下文内核态运行(即当前进程通过系统调用触发)。

    unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

    通常用在设备读函数或ioctl 中获取参数的函数中:其中“to”是用户空间的buffer地址,在本函数中将内核bufferfrom”除的n个字节拷贝到用户空间的“tobuffer

     

    unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)

    通常用在设备写函数或ioctl中设置参数的函数中:“to”是内核空间的buffer指针,要写入的buffer;“from”是用户空间的指针,数据源buffer

    注意:中断代码时不能用这两个函数,因为其调用了might_sleep()函数,会导致睡眠,并且这两个函数要求在进程上下文内核态空间中运行。

     

    这两个函数不能直接在中断中使用,但是可以变通一下,在中断中向进程发送信号通知进程有数据准备好。在进程执行时调用read函数,在read函数中调用copy_to_user函数,从而实现中断触发,把数据从内核空间拷贝到用户空间的需要。

     

    2.6 共享内存(mmap)

           使用mmap()函数通常映射一个普通文件实现进程之间内存共享,即多个进程打开同一个文件,将文件映射到各自进程的虚拟空间。这样各个进程就可以通过共享的内存进行大量的数据交互,当然需要我们自己设计互斥功能。

           还可以使用mmap()函数实现内核空间和用户空间内存共享的功能。网上提到的方法基本都是proc文件+mmap。

    大体过程如下

    1、在模块中申请一些内存页面,作为共享的内存空间。

    2、创建可读的proc文件,在其读函数中把上面申请的内存空间的物理地址返回给进程空间。

    3、在进程空间open /dev/mem文件,并把从proc读取的物理地址(要共享的内存的物理地址)作为文件/dev/mem的offset,以此offset 把/dev/mem文件的若干空间用mmap映射到进程空间。

    注意:

    1、/dev/mem 不是一个普通的文件里面的内容是所有物理内存的内容信息。所以,在上面的过程中把共享空间的物理地址作为offset使用。

    2、proc文件的作用就是提供一个读取的函数,把共享内存的地址从内核空间传递到用户空间。也可以用设备的ioctl 把该物理地址数值传给用户空间。


    展开全文
  • 多数的 Linux 内核态程序都需要和用户空间的进程交换数据,但 Linux 内核态无法对传统的 Linux 进程间同步和通信的方法提供足够的支持!本文就总结下常见的ipc, getsockopt/setsockopt mmap netlink/socket
    原文地址:http://bbs.chinaunix.net/thread-1940094-1-1.html
    多数的 Linux 内核态程序都需要和用户空间的进程交换数据,但 Linux 内核态无法对传统的 Linux 进程间同步和通信的方法提供足够的支持!本文就总结下常见的ipc,
    getsockopt/setsockopt     mmap      netlink/socket      proc/seq   copy_from_user/copy_to_user  文件。采用先讲解后测试代码的方式,netlink和proc由于江哥和段兄都写的比较好了我就贴了链接...  好了不废话了开始
      
       一.getsockopt/setsockopt
          最近看ebtables源码,发现与内核的ipc是采用的getsockopt,  具体实现是在内核中用nf_register_sockopt函数注册一个nf_sockopt_ops的结构体,比如说:

    1. static struct nf_sockopt_ops nso = {  
    2.      .pf  = PF_INET,       // 协议族  
    3.      .set_optmin = 常数,    // 定义最小set命令字  
    4.      .set_optmax = 常数+N,  // 定义最大set命令字  
    5.      .set  = do_nso_set,   // 定义set处理函数  
    6.      .get_optmin = 常数,    // 定义最小get命令字  
    7.      .get_optmax = 常数+N,  // 定义最大get命令字  
    8.      .get  = do_nso_get,   // 定义set处理函数  
    9. };  
    复制代码


    其中命令字不能与系统已有的命令字重复。set/get处理函数是直接由用户空间的set/getsockopt函数调用的。

    nf_sockopt.jpg 

    从这个图里面可以看出来,这种方法的本质就是调用是copy_from_user()/copy_to_user()方法完成内核和用户通信的,这样其实效率不高,多用在传递控制选项信息,不适合用做大量数据的传输。copy_from_user()/copy_to_user()我讲在后面介绍...  当然对于linux任何都是文件那么我想应该也是可以定义自己的ioctl的,这个在后面的
    copy_xx_user的块设备中讲解
      setsockopt/getsockopt  kernel部分代码:
      
    1.       static int recv_msg(struct sock *sk, int cmd, void *user, unsigned int len)
    2. {
    3.     int ret = 0;
    4.     printk(KERN_INFO "sockopt: recv_msg()\n"); 
    5.     /*
    6.     switch(cmd)
    7.     {
    8.     case IMP1_SET:
    9.     {
    10.         char umsg[64];
    11.         memset(umsg, 0, sizeof(char)*64);
    12.         copy_from_user(umsg, user, sizeof(char)*64);
    13.         printk("umsg: %s", umsg);
    14.     }
    15.     break;
    16.     }
    17.     */
    18.     if (cmd == SOCKET_OPS_SET)
    19.     {
    20.         char umsg[64];
    21.         int len = sizeof(char)*64;
    22.         memset(umsg, 0, len);
    23.         ret = copy_from_user(umsg, user, len);
    24.         printk("recv_msg: umsg = %s. ret = %d\n", umsg, ret);        
    25.     }
    26.     return 0;


    27. static int send_msg(struct sock *sk, int cmd, void *user, int *len)
    28. {
    29.     int ret = 0;
    30.     printk(KERN_INFO "sockopt: send_msg()\n"); 
    31.     if (cmd == SOCKET_OPS_GET)
    32.     {
    33.         ret = copy_to_user(user, KMSG, KMSG_LEN);
    34.         printk("send_msg: umsg = %s. ret = %d. success\n", KMSG, ret);    
    35.     }
    36.     return 0;


    37. static struct nf_sockopt_ops test_sockops =
    38. {
    39.     .pf = PF_INET,
    40.     .set_optmin = SOCKET_OPS_SET,
    41.     .set_optmax = SOCKET_OPS_MAX,
    42.     .set = recv_msg,
    43.     .get_optmin = SOCKET_OPS_GET,
    44.     .get_optmax = SOCKET_OPS_MAX,
    45.     .get = send_msg,
    46. }; 
    47.    
    复制代码


    setsockopt/getsockopt  user部分代码:
    1. /*call function recv_msg()*/
    2.     ret = setsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_SET, UMSG, UMSG_LEN);
    3.     printf("setsockopt: ret = %d. msg = %s\n", ret, UMSG);
    4.     len = sizeof(char)*64; 

    5.     /*call function send_msg()*/
    6.     ret = getsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_GET, kmsg, &len);
    7.     printf("getsockopt: ret = %d. msg = %s\n", ret, kmsg);
    8.     if (ret != 0)
    9.     {
    10.         printf("getsockopt error: errno = %d, errstr = %s\n", errno, strerror(errno));
    11.     } 
    复制代码



    二. mmap共享内存
      采用共享内存通信的一个显而易 见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而 共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就 解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存 中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的.
    kernel: 
    1.    #include <linux/config.h>
    2. #include <linux/module.h>
    3. #include <linux/moduleparam.h>
    4. #include <linux/init.h>

    5. #include <linux/kernel.h>   /* printk() */
    6. #include <linux/slab.h>   /* kmalloc() */
    7. #include <linux/fs.h>       /* everything... */
    8. #include <linux/errno.h>    /* error codes */
    9. #include <linux/types.h>    /* size_t */
    10. #include <linux/mm.h>
    11. #include <linux/kdev_t.h>
    12. #include <asm/page.h>
    13. #include <linux/cdev.h>
    14. #include <linux/device.h>
    15. #include <linux/gfp.h>

    16. static unsigned char *myaddr=NULL;
    17. static int simple_major = 0;
    18. module_param(simple_major, int, 0);


    19. MODULE_LICENSE("GPL");
    20. MODULE_AUTHOR("Kenthy@163.com."); 
    21. MODULE_DESCRIPTION("Kernel study and test."); 


    22. /*
    23. * Common VMA ops.
    24. */

    25. void simple_vma_open(struct vm_area_struct *vma)
    26. {
    27.         printk(KERN_NOTICE "Simple VMA open, virt %lx, phys %lx\n",
    28.                         vma->vm_start, vma->vm_pgoff << PAGE_SHIFT);
    29. }

    30. void simple_vma_close(struct vm_area_struct *vma)
    31. {
    32.         printk(KERN_NOTICE "Simple VMA close.\n");
    33. }

    34. struct page *simple_vma_nopage(struct vm_area_struct *vma,
    35.                 unsigned long address, int *type)
    36. {
    37.         struct page *pageptr;
    38.         unsigned long offset = (address - vma->vm_start); 
    39.         if (offset>PAGE_SIZE*2)
    40.         {
    41.                 printk("out of size\n");
    42.                 return NULL;
    43.         }
    44.         printk("in vma_nopage: offset=%u\n", offset);

    45.         if(offset<PAGE_SIZE) // the first page
    46.                 pageptr=virt_to_page(myaddr);
    47.         else        // the second page
    48.                 pageptr=virt_to_page(myaddr+PAGE_SIZE);

    49.         get_page(pageptr);

    50.         return pageptr;
    51. }

    52. static struct vm_operations_struct simple_nopage_vm_ops = {
    53.         .open =   simple_vma_open,
    54.                 .close =  simple_vma_close,
    55.                 .nopage = simple_vma_nopage,
    56. };


    57. static int simple_open (struct inode *inode, struct file *filp)
    58. {
    59.         return 0;
    60. }

    61. static int simple_release(struct inode *inode, struct file *filp)
    62. {
    63.         return 0;
    64. }

    65. static int simple_nopage_mmap(struct file *filp, struct vm_area_struct *vma)
    66. {
    67.         unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;

    68.         printk("enter simple_nopage_mmap: offset=%u, vma->vm_pgoff=%u\n", offset, vma->vm_pgoff);
    69.         if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC))
    70.                 vma->vm_flags |= VM_IO;
    71.         vma->vm_flags |= VM_RESERVED;

    72.         vma->vm_ops = &simple_nopage_vm_ops;
    73.         simple_vma_open(vma);
    74.         return 0;
    75. }

    76. /*
    77. * Set up the cdev structure for a device.
    78. */
    79. static void simple_setup_cdev(struct cdev *dev, int minor,
    80.                 struct file_operations *fops)
    81. {
    82.         int err, devno = MKDEV(simple_major, minor);

    83.         cdev_init(dev, fops);
    84.         dev->owner = THIS_MODULE;
    85.         dev->ops = fops;
    86.         err = cdev_add (dev, devno, 1);
    87.         /* Fail gracefully if need be */
    88.         if (err)
    89.                 printk (KERN_NOTICE "Error %d adding simple%d", err, minor);
    90. }

    91. static struct file_operations simple_nopage_ops = {
    92.         .owner   = THIS_MODULE,
    93.         .open    = simple_open,
    94.         .release = simple_release,
    95.         .mmap    = simple_nopage_mmap,
    96. };

    97. /*
    98. * We export two simple devices.  There's no need for us to maintain any
    99. * special housekeeping info, so we just deal with raw cdevs.
    100. */
    101. static struct cdev SimpleDevs;

    102. /*
    103. * Module housekeeping.
    104. */
    105. static int simple_init(void)
    106. {
    107.         int result;
    108.         //unsigned int addr1, addr2;
    109.         dev_t dev = MKDEV(simple_major, 0);

    110.         /* Figure out our device number. */
    111.         if (simple_major)
    112.                 result = register_chrdev_region(dev, 1, "simple_nopage");
    113.         else {
    114.                 result = alloc_chrdev_region(&dev, 0, 1, "simple_nopage");
    115.                 simple_major = MAJOR(dev);
    116.         }
    117.         if (result < 0) {
    118.                 printk(KERN_WARNING "simple_nopage: unable to get major %d\n", simple_major);
    119.                 return result;
    120.         }
    121.         if (simple_major == 0)
    122.                 simple_major = result;

    123.         /* Now set up two cdevs. */
    124.         simple_setup_cdev(&SimpleDevs, 0, &simple_nopage_ops);

    125.         myaddr = __get_free_pages(GFP_KERNEL, 1);
    126.         if (!myaddr)
    127.                 return -ENOMEM;
    128.         // for test
    129.         strcpy(myaddr, "1234567890");
    130.         strcpy(myaddr+PAGE_SIZE, "abcdefghij");
    131.         return 0;
    132. }


    133. static void simple_cleanup(void)
    134. {
    135.         cdev_del(&SimpleDevs);
    136.         unregister_chrdev_region(MKDEV(simple_major, 0), 1);
    137. }


    138. module_init(simple_init);
    139. module_exit(simple_cleanup);
    140.   
    复制代码


    user:
    1. #include </work/apue/ourhdr.h>
    2. #include <fcntl.h>
    3. #include <sys/mman.h>

    4. int main(int argc, char *argv[])
    5. {
    6.         int fdin, fdout;
    7.         void *src, *dst;
    8.         struct stat statbuf;
    9.         unsigned char sz[1024]={0};
    10.         if ((fdin = open("/dev/simple_nopage", O_RDONLY)) < 0)
    11.                 err_sys("can't open /dev/simple_nopage for reading");

    12.         if ((src = mmap(NULL, 4096*2, PROT_READ, MAP_SHARED,
    13.                                         fdin, 0)) == MAP_FAILED)
    14.                 err_sys("mmap error for simplen");

    15.         memcpy(sz, src, 11);
    16.         sz[10]='\0';
    17.         printf("%x\n", src);
    18.         printf("%s\n\n", sz);

    19.         memcpy(sz, src+4096, 11);
    20.         printf("%x\n", src+4096);
    21.         printf("%s\n", sz);

    22.         exit(0);
    23. }
    复制代码


    mmap加载文件后注意还要mknod

    三. netlink
      看看duanjigang兄的这两篇文章就可以了
    netlink socket 编程之 why & how 
      http://linux.chinaunix.net/bbs/viewthread.php?tid=1031932&extra=page%3D2%26amp%3Bfilter%3Ddigest
    使用netlink通讯时需要注意的一些问题   
    [url]http://linux.chinaunix.net/bbs/viewthread.php?tid=1144547&extra=page%3D2%26amp%3Bfilter%3Ddigest[/url]

    四. proc/seq
      记得proc和seq是我面试实习的时候一个小笔试题,当时小弟我很是无助在dreamice大哥的无私的指点,甚至可以说你替我完成了作业,小弟我真是惭愧,也正是下面两个帖子诞生的背景^_^
      proc文件系统剖析 
      http://linux.chinaunix.net/bbs/viewthread.php?tid=1044497&extra=page%3D2%26amp%3Bfilter%3Ddigest

    Seq_file File System实例剖析 
      http://linux.chinaunix.net/bbs/viewthread.php?tid=1044672&extra=page%3D2%26amp%3Bfilter%3Ddigest

    [ 本帖最后由 ubuntuer 于 2010-1-16 16:10 编辑 ]
    已有 1 人评分 可用积分 收起理由
     Godbach + 15 多谢分享

    总评分: 可用积分 + 15   查看全部评分

    http://ubuntuer.cublog.cn欢迎做做
     
       

    帖子
    2838
    主题
    668
    精华
    9
    可用积分
    41
    专家积分
    100
    在线时间
    1600 小时
    注册时间
    2008-08-15
    最后登录
    2011-12-01
    论坛徽章:
    0
    2[报告]
     发表于 2010-01-16 16:07:26 |只看该作者
    五. 文件
      使用文件来通信其实是有点牵强的,不过这个当时在bell的一个项目中,我确实这个干过就来出来了  
      
    1.    #include <linux/kernel.h> 
    2. #include <linux/module.h> 
    3. #include <linux/fs.h> 
    4. #include <asm/uaccess.h> 
    5. #include <linux/mm.h> 

    6. MODULE_LICENSE("GPL");
    7. MODULE_AUTHOR("Kenthy@163.com."); 
    8. MODULE_DESCRIPTION("Kernel study and test."); 

    9. struct file *filp; 
    10. mm_segment_t fs;
    11.   
    12. void SLEEP_MILLI_SEC(int nMilliSec)

    13. long timeout = (nMilliSec) * 100 / 1000; 
    14.   while(timeout > 0) 
    15.    { 
    16.      timeout = schedule_timeout(timeout); 
    17.    } 


    18. void fileread(const char * filename) 

    19.   struct file *filp; 
    20.   struct inode *inode; 
    21.   mm_segment_t fs; 
    22.   off_t fsize; 
    23.   char *buf; 
    24.   unsigned long magic; 
    25.   filp=filp_open(filename,O_RDONLY,0); 
    26.   inode=filp->f_dentry->d_inode; 
    27.   
    28.   magic=inode->i_sb->s_magic; 
    29.   printk("file system magic:%li \n",magic); 
    30.   printk("super blocksize:%li \n",inode->i_sb->s_blocksize); 
    31.   printk("inode %li \n",inode->i_ino); 
    32.   fsize=inode->i_size; 
    33.   printk("file size:%i \n",(int)fsize); 
    34.   buf=(char *) kmalloc(fsize+1,GFP_ATOMIC); 

    35.   fs=get_fs(); 
    36.   set_fs(KERNEL_DS); 
    37.   filp->f_op->read(filp,buf,fsize,&(filp->f_pos)); 
    38.   set_fs(fs); 

    39.   buf[fsize]='\0'; 
    40.   printk("The File Content is:\n"); 
    41.   printk("%s\n",buf); 

    42.   kfree(buf);
    43.   filp_close(filp,NULL); 


    44. void filewrite(char* filename, char* data)
    45. {
    46.   fs=get_fs();
    47.   set_fs(KERNEL_DS);
    48.   filp->f_op->write(filp, data, strlen(data),&filp->f_pos);
    49.   set_fs(fs);
    50. }

    51. void test_write(char* filename)
    52. {
    53. int i;
    54. char data[20];
    55.   filp = filp_open(filename, O_RDWR|O_APPEND, 0644);
    56.   if(IS_ERR(filp))
    57.    {
    58.     printk("open error...\n"); 
    59.     return;
    60.    } 


    61. for(i=0;i<10;i++)
    62. {
    63.   sprintf(data, "%s, %d\n", "kernel write test", i);
    64.   filewrite(filename, data);
    65.   SLEEP_MILLI_SEC(1000);
    66. }
    67. }

    68. int init_module() 

    69.   char *filename="/root/log"; 

    70.   printk("%s\n", "begin write data");
    71.   test_write(filename);
    72.   
    73.   printk("%s\n", "begin read data");
    74.   fileread(filename); 
    75.   return 0; 


    76. void cleanup_module() 

    77.   filp_close(filp,NULL);
    78.   printk("Good,Bye!\n"); 

    79.    
    复制代码


       上面的代码还附送了内核中如何sleep 

    六. copy_xxx_user
      copy_from_user函数的目的是从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0.这么简单的一个函数却含盖了许多关于内核方面的知识,比如内核关于异常出错的处理.从用户空间拷贝数据到内核中时必须很小心,假如用户空间的数据地址是个非法的地址,或是超出用户空间的范围,或是那些地址还没有被映射到,都可能对内核产生很大的影响,如oops,或被造成系统安全的影响.所以copy_from_user函数的功能就不只是从用户空间拷贝数据那样简单了,他还要做一些指针检查连同处理这些
    问题的方法. 函数原型在[arch/i386/lib/usercopy.c]中
      
    1. unsigned long
    2. copy_from_user(void *to, const void __user *from, unsigned long n)
    3. {
    4.     might_sleep();    
    5.     if (access_ok(VERIFY_READ, from, n))
    6.         n = __copy_from_user(to, from, n);
    7.     else
    8.         memset(to, 0, n);
    9.     return n;
    10. }
    11.   
    复制代码

      具体的代码实现我就不讲了,我自己也看的懵懂!!!还是来使用使用吧
    1. #include <linux/kernel.h>
    2. #include <linux/module.h>
    3. #include <linux/init.h>
    4. #include <linux/cdev.h>
    5. #include <linux/fs.h>
    6. #include <linux/sched.h>
    7. #include <asm/uaccess.h>

    8. MODULE_LICENSE("GPL");
    9. MODULE_AUTHOR("Kenthy@163.com."); 
    10. MODULE_DESCRIPTION("Kernel study and test."); 


    11. #define DP_MAJOR 251//主设备号
    12. #define DP_MINOR 0  //次设备号
    13. #define DEV_NAME "kenthy"

    14. static int char_read(struct file *filp, char __user *buffer, size_t, loff_t*);
    15. static int char_open(struct inode*, struct file*);
    16. static int char_write(struct file *filp, const char __user *buffer, size_t, loff_t*);
    17. static int char_release(struct inode*, struct file*);

    18. static int chropen;
    19. struct cdev *chardev;
    20. static int len;

    21. static char *to;
    22. static const struct file_operations char_ops={
    23.     .read = char_read,
    24.     .write = char_write,
    25.     .open = char_open,
    26.     .release = char_release,
    27. };

    28. static int __init char_init(void)
    29. {
    30.     dev_t dev;
    31.     printk(KERN_ALERT"Initing......\n");
    32.     dev = MKDEV(DP_MAJOR, DP_MINOR);
    33.     chardev = cdev_alloc();

    34.     if(chardev == NULL){
    35.         return -1;
    36.     }
    37.     if(register_chrdev_region(dev, 10, DEV_NAME)){
    38.         printk(KERN_ALERT"Register char dev error\n");
    39.         return -1;
    40.     }
    41.     chropen = 0;
    42.     len = 0;
    43.     cdev_init(chardev, &char_ops);
    44.     if(cdev_add(chardev, dev, 1)){
    45.         printk(KERN_ALERT"Add char dev error!\n");
    46.     }
    47.     return 0;
    48. }

    49. static int char_open(struct inode *inode, struct file *file)
    50. {
    51.     if(chropen == 0)
    52.         chropen++;
    53.     else{
    54.         printk(KERN_ALERT"Another process open the char device\n");
    55.         return -1;
    56.     }
    57.     try_module_get(THIS_MODULE);
    58.     return 0;
    59. }

    60. static int char_release(struct inode *inode,struct file *file)
    61. {
    62.     chropen--;
    63.     module_put(THIS_MODULE);
    64.     return 0;
    65. }

    66. static int char_read(struct file *filp,char __user *buffer,size_t length,loff_t *offset)
    67. {
    68.     unsigned long nn;
    69.     nn = copy_to_user(buffer, to, length);
    70.     printk("nn = %ld\n", nn);
    71.     printk("buffer = %s\n", buffer);
    72.     return length;
    73. }

    74. static int char_write(struct file *filp, const char __user *buffer, size_t length, loff_t *offset)
    75. {
    76.     unsigned long n;
    77.     to = (char *)kmalloc((sizeof(char)) * (length+1), GFP_KERNEL);
    78.     memset(to, '\0', length+1);
    79.     n = copy_from_user(to, buffer, length);
    80.     printk("n = %ld\n", n);
    81.     printk("to = %s\n", to);
    82.     return length;
    83. }

    84. static void __exit module_close(void)
    85. {
    86.         len=0;
    87.         printk(KERN_ALERT"Unloading..........\n");

    88.         unregister_chrdev_region(MKDEV(DP_MAJOR,DP_MINOR),10);
    89.         cdev_del(chardev);
    90. }

    91. module_init(char_init);
    92. module_exit(module_close);
    复制代码

    这个与mmap的类似都依赖于一个字符设备文件
    sudo insmod chardev.ko
    在使用测试程序前我们要创建一个字符设备:
    #mknod  /dev/chardev0  c  251  0
    测试程序代码如下:
    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <unistd.h>
    5. #include <fcntl.h>
    6. int main(int args, char *argv[])
    7. {
    8.     int testdev;
    9.     int i, rf = 0;
    10.     char buf[15];

    11.     memset(buf, '\0', 15);
    12.     testdev = open("/dev/chardev0", O_RDWR);
    13.     if(testdev == -1){
    14.         perror("open\n");
    15.         exit(0);
    16.     }
    17.     if((write(testdev, "1111111111", 10)) < 0){
    18.         perror("Write error!\n");
    19.         exit(0);
    20.     }
    21.     close(testdev);
    22.     printf("write finish!\n");
    23.     testdev = open("/dev/chardev0", O_RDWR);    
    24.     rf = read(testdev, buf, 3);
    25.     if(rf < 0)
    26.         perror("read error\n");
    27.     printf("Read: %s\n", buf);
    28.     close(testdev);
    29.     
    30.     return 0;
    31. }
    复制代码


    运行程序:
    #./test
    Read: 111
    看一下内核信息:
    #dmesg
    最后几行信息如下:
    nn = 0
    buffer = 1111111111
    n = 0
    to = 111
    这就说明设备注册正确,从用户到内核copy_from_user和从内核到用户copy_to_user都顺利完成了。呵呵,你是不是觉得还少了点什么东西,对我前面说还有ioctl没有讲
    在内核空间中ioctl是很多内核操作结构的一个成员函数,如文件操作结构struct file_operations(include/linux/fs.h)、协议操作结构struct proto_ops(include/linux/net.h)等、tty操作结构struct tty_driver(include/linux/tty_driver.h)等,而这些操作结构分别对应各种内核设备,只要在用户空间打开这些设备,如I/O设备可用open(2)打开,网络协议可用socket(2)打开等,获取一个文件描述符后,就可以在这个描述符上调用ioctl(2)来向内核交换数据。
    ioctl(2)函数的基本使用格式为:
    int ioctl(int fd, int cmd, void *data)
    第一个参数是文件描述符;cmd是操作命令,一般分为GET、SET以及其他类型命令,GET是用户空间进程从内核读数据,SET是用户空间进程向内核写数据,cmd虽然是一个整数,但是有一定的参数格式的,下面再详细说明;第三个参数是数据起始位置指针,
    cmd命令参数是个32位整数,分为四部分:
    dir(2b)  size(14b)  type(8b) nr(8b)
    详细定义cmd要包括这4个部分时可使用宏_IOC(dir,type,nr,size)来定义,而最简单情况下使用_IO(type, nr)来定义就可以了,这些宏都在include/asm/ioctl.h中定义
    本文cmd定义为:
    #define NEWCHAR_IOC_MAGIC   'M'
    #define NEWCHAR_SET    _IO(NEWCHAR_IOC_MAGIC, 0)
    #define NEWCHAR_GET    _IO(NEWCHAR_IOC_MAGIC, 1)
    #define NEWCHAR_IOC_MAXNR   1

    要定义自己的ioctl操作,可以有两个方式,一种是在现有的内核代码中直接添加相关代码进行支持,比如想通过socket描述符进行ioctl操作,可在net/ipv4/af_inet.c中的inet_ioctl()函数中添加自己定义的命令和相关的处理函数,重新编译内核即可,不过这种方法一般不推荐;第二种方法是定义自己的内核设备,通过设备的ioctl()来操作,可以编成模块,这样不影响原有的内核,这是最通常的做法。
       大致过程如下
    1. 进行ioctl调用的基本处理函数
    2. static int newchar_ioctl(struct inode *inode, struct file *filep, 
    3.       unsigned int cmd, unsigned long arg)
    4. {
    5. int  ret;
    6. // 首先检查cmd是否合法
    7. if (_IOC_TYPE(cmd) != NEWCHAR_IOC_MAGIC) return -EINVAL;
    8. if (_IOC_NR(cmd) > NEWCHAR_IOC_MAXNR) return -EINVAL;
    9. // 错误情况下的缺省返回值
    10. ret = EINVAL;
    11. switch(cmd)
    12. {
    13. case KNEWCHAR_SET:
    14. // 设置操作,将数据从用户空间拷贝到内核空间
    15.   {
    16.    struct  newchar  nc;
    17.    if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
    18.     return -EFAULT;
    19.    ret = do_set_newchar(&nc);
    20.   }
    21.   break;
    22. case KNEWCHAR_GET:
    23. // GET操作通常会在数据缓冲区中先传递部分初始值作为数据查找条件,获取全部
    24. // 数据后重新写回缓冲区
    25. // 当然也可以根据具体情况什么也不传入直接向内核获取数据
    26.   {
    27.    struct  newchar  nc;
    28.    if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
    29.     return -EFAULT;
    30.    ret = do_get_newchar(&nc);
    31.    if(ret == 0){
    32.     if(copy_to_user((unsigned char *)arg, &nc, sizeof(nc))!=0)
    33.      return -EFAULT;
    34.    }
    35.   }
    36.   break;
    37. }
    38. return ret;
    39. }
    复制代码


      由于能力有限,难免会有错误和纰漏,还望给位海涵并指正, 当然也还存在其他的user kernel ipc, 也希望给位指出来。工作量比较大我的代码很多都只是提供了一种思路,要像深究研究的话,还必须DIY了!!!马上放寒假了,要回家过最后一个舒服的大年了,也要离开cu一段时间了,念念不舍啊...

    参考文献:
       Linux 系统内核空间与用户空间通信的实现与分析:
        http://www.ibm.com/developerworks/cn/linux/l-netlink/index.html
       Linux Netfilter实现机制和扩展技术:
        [url]http://www.ibm.com/developerworks/cn/linux/l-ntflt/index.html[/url]
    展开全文
  • linux驱动程序一般工作在内核空间,但也可以工作在用户空间。下面我们将详细解析,什么是内核空间,什么是用户空间,以及如何判断他们。  Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的...
  • 但是注意虽然Netlink主要用于用户空间和内核空间通信,但是也能用于用户空间的两个进程通信。只是进程间通信有其他很多方式,一般不用Netlink。除非需要用到Netlink的广播特性时。 那么Netlink有什么优势呢? ...
  • 一、netlink简介 Netlink是linux继承于unix的一种基于socket的内核态与用户态进程间通信(PCI)机制。linux的进程间通信有多种机制,比方管道(Pipe)、共享内存(shmget)等。...linux中支持用户和内核态的进程通信方式还
  • linux驱动程序一般工作在内核空间,但也可以工作在用户空间,内核空间和用户空间之间如何进行通讯? 1、系统调用 read,write,ioctl A.get_user(x,ptr):在内核中被调用,获取用户空间指定地址的数值并保存到...
  • Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。 Netlink 是一种特殊的 socket,它是 Linux 所特有的,类似于 BSD 中的AF_ROUTE 但又远比...
  • linux内核空间用户空间通信

    千次阅读 2015-11-16 20:22:55
    用户空间主动发起的信息交互编写自己的系统调用目前linux大致提供了两百多个标准的系统调用,并且允许我们添加自己的系统调用来实现和内核的信息交互。编写驱动程序read, write, ioctl使用/proc等文件系统使用mmap...
  • Linux内核和用户空间通信的方法

    千次阅读 2014-07-14 23:09:39
    Linux内核和用户空间通信的方法(二)— 使用netlink   作者:Kendo 2006-9-3 这是一篇学习笔记,主要是对《Linux 系统内核空间用户空间通信的实现与分析》中的源码imp2...
  • Linux内核用户空间通信的方式目前主要有9种,分别是内核启动参数、模块参数与 sysfs、sysctl、系统调用、netlink、procfs、seq_file、debugfsrelayfs。Netlink是一种特殊的文件描述符(套结字),为2.6.14及更高...
  • 多数的 Linux 内核态程序都需要和用户空间的进程交换数据,但 Linux 内核态无法对传统的 Linux 进 程间同步和通信的方法提供足够的支持。本文总结并比较了几种内核态与用户态进程通信的实现方法,并 推荐使用 ...
  • Linux_系统内核空间与用户空间通信的实现与分析,可以让您更加深入的了解用户空间和内核空间之间的通信!
  • http://blog.sina.com.cn/s/blog_71fdf1f00102v5o8.html引言一般地,在使用虚拟内存技术的多任务系统上,内核和应用有不同的地址空间,因此,在内核和应用之间以及在应用与应用之间进行数据交换需要专门的机制来实现...
  • 一般地,在使用虚拟内存技术的多任务系统上,内核和应用有不同的地址空间,因此,在内核和应用之间以及在应用与应用之间进行数据交换需要专门的机制来实现,众所周知,进程间通信(IPC)机制就是为实现应用与应用...
  • 这是一篇学习笔记,主要是对《Linux 系统内核空间用户空间通信的实现与分析》中的源码imp2的分析。其中的源码,可以到以下URL下载:  http://www-128.ibm.com/developerworks/cn/linux/l-netlink/imp2.tar.gz...
  • 我们平常在写代码时,一般是在用户空间,通过系统调用函数来访问内核空间,这是最常用的一种用户态和内核通信的方式。(关于 Linux 用户态和内核态可以参考 xx) 除此之外,还有以下四种方式: procfs(/proc) ...
  • 2. 关键过程和根本原因分析/proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux内核空间和用户空间之间进行通信。在 /proc 文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一...
  • linux 用户和内核通信之Netlink

    千次阅读 2012-09-14 16:57:39
    之前参考这篇帖子练习了一下netlink,当时记得可以正常调试成功,今天在自己笔记本上ubuntu 12.04 上调试了一下,结果一直出现kernel panic, 调试了一下发现应该是出现在kernel 向用户空间发送消息的流程中。...
  • 其中SIOCDEVPRIVATE命令其他的在0x89F0到0x89FF之间的代码将出现在switch语句中的一个分支——default语句中,代码最后还增加了对无线网络的支持。 内核 执行时会检查表示设备的结构变量中,是否已经定义了一个与...
  • 最近在看代码的过程中看到了一种使用很广的Linux内核空间和用户空间通信的一种机制,发现下面的这篇文章很是不错,特别转出来,给大家分享。  转自http://www.cnblogs.com/iceocean/articles/1594195.html LINUX
  • 这是一篇学习笔记,主要是对《Linux系统内核空间用户空间通信的实现与分析》中的源码imp2的分析。其中的源码,可以到以下URL下载: http://www-128.ibm.com/developerworks/cn/Linux/l-netlink/im...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,181
精华内容 472
关键字:

linux用户空间和内核空间通信

linux 订阅