zoukankan      html  css  js  c++  java
  • 内核和进程通信方式


    内核和用户空间进行通信,大概有如下几种方式可以考虑:
    采用内存映射的方式,将内核地址映射到用户态。这种方式最直接,可以适用大量的数据传输机制。这种方式的缺点是很难进行“业务控制”,没有一种可靠的机制 保障内核和用户态的调动同步,比如信号量等都不能跨内核、用户层使用。因此内存映射机制一般需要配合一种“消息机制”来控制数据的读取,比如采用“消息” 类型的短数据通道来完成一个可靠的数据读取功能。
    ioctl机制,ioctl机制可以在驱动中扩展特定的ioctl消息,用于将一些状态从内核反应到用户态。Ioctl有很好的数据同步保护机制,不要担 心内核和用户层的数据访问冲突,但是ioctl不适合传输大量的数据,通过和内存映射结合可以很好的完成大量数据交换过程。但是,ioctl的发起方一定 是在用户态,因此如果需要内核态主动发起一个通知消息给用户层,则非常的麻烦。可能需要用户态程序采用轮询机制不停的ioctl。
    其他一些方式比如系统调用必须通过用户态发起,proc方式不太可靠和实时,用于调试信息的输出还是非常合适的。
    通过前面的项目背景,我需要一种可以在内核态主动发起消息的通知方式,而用户态的程序最好可以采用一种“阻塞调用”的方式等待消息。这样的模型可以最大限度的节省CPU的调度,同时可以满足及时处理的要求,最终选择了netlink完成通信的过程。
    Netlink的通信模型和socket通信非常相似,主要要点如下:

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



    Netlink的通信地址和协议

    所有socket之间的通信,必须有个地址结构,Netlink也不例外。我们最熟悉的就是IPV4的地址了,netlink的地址结构如下:

    1. struct sockaddr_nl  
    2. {  
    3.     sa_family_t nl_family;          //必须为AF_NETLINK或者PF_NETLINK  
    4.     unsigned short  nl_pad;             //必须为0  
    5.     __u32       nl_pid;             //通信端口  
    6. __u32       nl_groups;              //组播掩码  
    7. };  


    上面几个数据,最关键的是nl_family(就对应IP通信中的AF_INET)和nl_pid。

    nl_pid就是一个约定的通信端口,用户态使用的时候需要用一个非0的数字,一般来 说可以直接采用上层应用的进程ID(不用进程ID号码也没事,只要系统中不冲突的一个数字即可使用)。对于内核的地址,该值必须用0,也就是说,如果上层 通过sendto向内核发送netlink消息,peer addr中nl_pid必须填写0。


    nl_groups用于一个消息同时分发给不同的接收者,是一种组播应用,本文不讲组播应用。


    本质上,nl_pid就是netlink的通信地址。除了通信地址,netlink还提供“协议”来标示通信实体,在创建socket的时候,需要指定 netlink的通信协议号。每个协议号代表一种“应用”,上层可以用内核已经定义的协议和内核进行通信,获得内核已经提供的信息。具体支持的协议列表如 下:

    1. #define NETLINK_ROUTE       0   /* Routing/device hook              */  
    2. #define NETLINK_UNUSED      1   /* Unused number                */  
    3. #define NETLINK_USERSOCK    2   /* Reserved for user mode socket protocols  */  
    4. #define NETLINK_FIREWALL    3   /* Firewalling hook             */  
    5. #define NETLINK_INET_DIAG   4   /* INET socket monitoring           */  
    6. #define NETLINK_NFLOG       5   /* netfilter/iptables ULOG */  
    7. #define NETLINK_XFRM        6   /* ipsec */  
    8. #define NETLINK_SELINUX     7   /* SELinux event notifications */  
    9. #define NETLINK_ISCSI       8   /* Open-iSCSI */  
    10. #define NETLINK_AUDIT       9   /* auditing */  
    11. #define NETLINK_FIB_LOOKUP  10    
    12. #define NETLINK_CONNECTOR   11  
    13. #define NETLINK_NETFILTER   12  /* netfilter subsystem */  
    14. #define NETLINK_IP6_FW      13  
    15. #define NETLINK_DNRTMSG     14  /* DECnet routing messages */  
    16. #define NETLINK_KOBJECT_UEVENT  15  /* Kernel messages to userspace */  
    17. #define NETLINK_GENERIC     16  
    18. /* leave room for NETLINK_DM (DM Events) */  
    19. #define NETLINK_SCSITRANSPORT   18  /* SCSI Transports */  
    20. #define NETLINK_ECRYPTFS    19  


    协议的用途很好理解,比如我们单纯创建一个上层应用,通过和 NETLINK_ROUTE协议通信,可以获得内核的路由信息。我需要利用netlink创建一个我自己的通信协议,因此我定义了一种新的协议。新协议的 定义不能和内核已经定义的冲突,同时不能超过MAX_LINKS这个宏的限定,MAX_LINKS = 32。所以我定义的协议号为30。


    小结:netlink采用协议号+通信端口的方式构建自己的地址体系。


    用户态操作netlink socket

    用户态创建netlink socket的基本过程和操作其他socket的API一模一样,区别就2点:
    1、 netlink有自己的地址;
    2、 netlink接收到的消息带一个netlink自己的消息头;


    用户态创建、销毁socket的过程:
    1、 用socket函数创建,socket(PF_NETLINK, SOCK_DGRAM, NETLINK_XXX);第一个参数必须是PF_NETLINK或者AF_NETLINK,第二个参数用SOCK_DGRAM和SOCK_RAW都没问 题,第三个参数就是netlink的协议号。
    2、 用bind函数绑定自己的地址。
    3、 用close关闭套接字。

    创建socket的代码样例:

    1. {  
    2.     struct sockaddr_nl addr;  
    3.     int flags;  
    4.      
    5.     //建立netlink socket  
    6.     s_nlm_socket = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_XXX);  
    7.     if(s_nlm_socket < 0)  
    8.     {  
    9.         USE_DBG_OUT("create netlink socket error. ");  
    10.         goto Err_Exit;  
    11.     }  
    12.       
    13.     //bind  
    14.     addr.nl_family = PF_NETLINK;  
    15.     addr.nl_pad    = 0;  
    16.     addr.nl_pid    = getpid();  
    17.     addr.nl_groups = 0;  
    18.       
    19.     if(bind(s_nlm_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0)  
    20.     {  
    21.         USE_DBG_OUT("bind socket error. ");  
    22.         goto Err_Exit;  
    23.     }  
    24.   
    25.   
    26.     //设置socket为非阻塞模式  
    27.     flags = fcntl(s_nlm_socket, F_GETFL, 0);  
    28.     fcntl(s_nlm_socket, F_SETFL, flags|O_NONBLOCK);  
    29.       
    30.   
    31.   
    32.     return 0;  
    33. Err_Exit:  
    34.     return -1;  
    35. }  



    用户态接收、发送消息的API:
    用户态用sendto向内核发送netlink消息,用recvfrom接收消息。只是注意,发送、接收的时候在自己附带的消息前面要加上一个netlink的消息头。例如,定义一个如下的消息通信结构:

    1. struct tag_rcv_buf  
    2. {  
    3.         struct nlmsghdr hdr;            //netlink的消息头  
    4.         netlink_notify_s my_msg;        //通信实体消息  
    5. }st_snd_buf;  


    发送代码的例子:

    1. My_send_msg  
    2. {  
    3.     struct tag_rcv_buf  
    4.     {  
    5.         struct nlmsghdr hdr;            //netlink的消息头  
    6.         netlink_notify_s my_msg;        //通信实体消息  
    7.     }st_snd_buf;  
    8.     fd_set st_write_set;                         //select fd,避免线程吊死  
    9.     struct timeval write_time_out = {10, 0};     //10秒超时  
    10.     int ret;  
    11.       
    12.     //设置select  
    13.     FD_ZERO(&st_write_set);  
    14.     FD_SET(s_nlm_socket, &st_write_set);  
    15.       
    16.     /* 
    17.         设置发送数据 
    18.     */  
    19.     st_snd_buf.hdr.nlmsg_len   = sizeof(st_snd_buf);        //NLMSG_LENGTH(sizeof(netlink_notify_s))--这个宏包含有头  
    20.     st_snd_buf.hdr.nlmsg_flags = 0;                         /*消息的附加选项,没啥用*/  
    21.     st_snd_buf.hdr.nlmsg_type  = 0;                         /*设置自定义消息类型*/  
    22.     st_snd_buf.hdr.nlmsg_pid   = getpid();                  /*设置发送者的PID*/  
    23.   
    24.   
    25.     st_snd_buf.my_msg.start_pack_id = s_id;  
    26.     st_snd_buf.my_msg.end_pack_id   = e_id;  
    27.       
    28.     ret = select(s_nlm_socket+1, NULL, &st_write_set, NULL, &write_time_out);  
    29.     if(ret == -1)  
    30.     {  
    31.         //have some error.  
    32.         USE_DBG_OUT("send has some error %d. ", errno);  
    33.         goto out;  
    34.     }  
    35.     else if(ret == 0)  
    36.     {  
    37.         //超时退出  
    38.         TMP_DBG_OUT("send timeout. ");  
    39.         goto out;  
    40.     }  
    41.     else  
    42.     {  
    43.         //接收消息  
    44.         ret = sendto(s_nlm_socket, &st_snd_buf, sizeof(st_snd_buf), 0,   
    45.                         (struct sockaddr*)&s_peer_addr, sizeof(s_peer_addr));  
    46.   
    47.   
    48.         if(ret < 0)  
    49.         {  
    50.             USE_DBG_OUT("send to kernal by nl error %d ", errno);  
    51.         }  
    52.         else  
    53.         {  
    54.             TMP_DBG_OUT("send to kernal ok s_id is %d, e_id is %d. ", s_id, e_id);  
    55.         }  
    56.     }  
    57.       
    58. out:      
    59.     return;  
    60. }  


    接收数据的代码例子:

    1. {  
    2.     struct tag_rcv_buf  
    3.     {  
    4.         struct nlmsghdr hdr;            //netlink的消息头  
    5.         netlink_notify_s my_msg;        //通信实体消息  
    6.     }st_rcv_buf;  
    7.     int ret, addr_len, io_ret;  
    8.     struct sockaddr_nl st_peer_addr;  
    9.     fd_set st_read_set;                         //select fd,避免线程吊死  
    10.     struct timeval read_time_out = {10, 0};     //10秒超时  
    11.     int rcv_buf;  
    12.       
    13.     //设置内核的通信地址  
    14.     st_peer_addr.nl_family = AF_NETLINK;  
    15.     st_peer_addr.nl_pad = 0;                                   /*always set to zero*/  
    16.     st_peer_addr.nl_pid = 0;                                   /*kernel's pid is zero*/  
    17.     st_peer_addr.nl_groups = 0;                                /*multicast groups mask, if unicast set to zero*/  
    18.     addr_len = sizeof(st_peer_addr);  
    19.   
    20.     //设置select  
    21.     FD_ZERO(&st_read_set);  
    22.     FD_SET(s_nlm_socket, &st_read_set);  
    23.   
    24.     ret = select(s_nlm_socket+1, &st_read_set, NULL, NULL, &read_time_out);  
    25.     if(ret == -1)  
    26.     {  
    27.         //have some error.  
    28.         USE_DBG_OUT("select rcv some error %d", errno);  
    29.         goto err;  
    30.     }  
    31.     else if(ret == 0)  
    32.     {  
    33.         //超时退出  
    34.         TMP_DBG_OUT("rcv timeout. ");  
    35.         *p_size = 0;  
    36.         goto out;  
    37.     }  
    38.     else  
    39.     {  
    40.         //接收消息  
    41.         ret = recvfrom(s_nlm_socket, &st_rcv_buf, sizeof(st_rcv_buf), 0,   
    42.             (struct sockaddr *)&st_peer_addr, &addr_len);  
    43.     }  
    44.      
    45.     if(ret == sizeof(st_rcv_buf) )  
    46.     {  
    47.         //收到消息了...  
    48.   
    49.     else  
    50.     {  
    51.         USE_DBG_OUT("rcv msg have some err. ret is %d, errno is %d ", ret, errno);  
    52.         goto err;  
    53.     }  
    54.   
    55. out:      
    56.     return 0;  
    57. err:  
    58.     *p_size = 0;  
    59.     return -1;  
    60. }  
  • 相关阅读:
    Android Studio打开出现:Default activity not found
    关于LayoutInflater的错误用法(警告提示:Avoid passing null as the view root)
    获取屏幕的宽和高-Display中getHeight()和getWidth() 官方已废弃
    setUserVisibleHint-- fragment真正的onResume和onPause方法
    Fragment的setUserVisibleHint方法实现懒加载,但setUserVisibleHint 不起作用?
    Android权限判断checkPermission
    Android:安装时提示:INSTALL_FAILED_INSUFFICIENT_STORAGE
    Android M新的运行时权限开发者需要知道的一切
    Android开发——Android M(6.0) 权限解决方案
    三个案例带你看懂LayoutInflater中inflate方法两个参数和三个参数的区别
  • 原文地址:https://www.cnblogs.com/oracleloyal/p/5329064.html
Copyright © 2011-2022 走看看