Linux 用户态与内核态的交互.docx
- 文档编号:8975539
- 上传时间:2023-02-02
- 格式:DOCX
- 页数:18
- 大小:27.65KB
Linux 用户态与内核态的交互.docx
《Linux 用户态与内核态的交互.docx》由会员分享,可在线阅读,更多相关《Linux 用户态与内核态的交互.docx(18页珍藏版)》请在冰豆网上搜索。
Linux用户态与内核态的交互
Linux用户态与内核态的交互
——netlink篇
作者:
Kendo
2006-9-3
这是一篇学习笔记,主要是对《Linux系统内核空间与用户空间通信的实现与分析》中的源码imp2的分析。
其中的源码,可以到以下URL下载:
参考文档
《Linux系统内核空间与用户空间通信的实现与分析》 陈鑫
《在Linux下用户空间与内核空间数据交换的方式》 杨燚
理论篇
在Linux2.4版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用netlink套接字实现的,例如iprote2网络管理工具,它与内核的交互就全部使用了netlink,著名的内核包过滤框架Netfilter在与用户空间的通读,也在最新版本中改变为netlink,无疑,它将是Linux用户态与内核态交流的主要方法之一。
它的通信依据是一个对应于进程的标识,一般定为该进程的ID。
当通信的一端处于中断过程时,该标识为0。
当使用netlink套接字进行通信,通信的双方都是用户态进程,则使用方法类似于消息队列。
但通信双方有一端是中断过程,使用方法则不同。
netlink套接字的最大特点是对中断过程的支持,它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线程,而是通过另一个软中断调用用户事先指定的接收函数。
工作原理如图:
如图所示,这里使用了软中断而不是内核线程来接收数据,这样就可以保证数据接收的实时性。
当netlink套接字用于内核空间与用户空间的通信时,在用户空间的创建方法和一般套接字使用类似,但内核空间的创建方法则不同,下图是netlink套接字实现此类通信时创建的过程:
用户空间
用户态应用使用标准的socket与内核通讯,标准的socketAPI的函数,socket(),bind(),sendmsg(),recvmsg()和close()很容易地应用到netlinksocket。
为了创建一个netlinksocket,用户需要使用如下参数调用socket():
1.socket(AF_NETLINK,SOCK_RAW,netlink_type)
复制代码
netlink对应的协议簇是AF_NETLINK,第二个参数必须是SOCK_RAW或SOCK_DGRAM,第三个参数指定netlink协议类型,它可以是一个自定义的类型,也可以使用内核预定义的类型:
1.#defineNETLINK_ROUTE 0 /*Routing/devicehook */
2.#defineNETLINK_W1 1 /*1-wiresubsystem */
3.#defineNETLINK_USERSOCK 2 /*Reservedforusermodesocketprotocols */
4.#defineNETLINK_FIREWALL 3 /*Firewallinghook */
5.#defineNETLINK_INET_DIAG 4 /*INETsocketmonitoring */
6.#defineNETLINK_NFLOG 5 /*netfilter/iptablesULOG*/
7.#defineNETLINK_XFRM 6 /*ipsec*/
8.#defineNETLINK_SELINUX 7 /*SELinuxeventnotifications*/
9.#defineNETLINK_ISCSI 8 /*Open-iSCSI*/
10.#defineNETLINK_AUDIT 9 /*auditing*/
11.#defineNETLINK_FIB_LOOKUP 10
12.#defineNETLINK_CONNECTOR 11
13.#defineNETLINK_NETFILTER 12 /*netfiltersubsystem*/
14.#defineNETLINK_IP6_FW 13
15.#defineNETLINK_DNRTMSG 14 /*DECnetroutingmessages*/
16.#defineNETLINK_KOBJECT_UEVENT15 /*Kernelmessagestouserspace*/
复制代码
#defineNETLINK_GENERIC 16
同样地,socket函数返回的套接字,可以交给bing等函数调用:
1.staticintskfd;
2.skfd=socket(PF_NETLINK,SOCK_RAW,NL_IMP2);
3.if(skfd<0)
4.{
5. printf("cannotcreateanetlinksocket\n");
6. exit(0);
7.}
复制代码
bind函数需要绑定协议地址,netlink的socket地址使用structsockaddr_nl结构描述:
1.structsockaddr_nl
2.{
3. sa_family_t nl_family;
4. unsignedshortnl_pad;
5. __u32 nl_pid;
6. __u32 nl_groups;
7.};
复制代码
成员nl_family为协议簇AF_NETLINK,成员nl_pad当前没有使用,因此要总是设置为0,成员nl_pid为接收或发送消息的进程的ID,如果希望内核处理消息或多播消息,就把该字段设置为0,否则设置为处理消息的进程ID。
成员nl_groups用于指定多播组,bind函数用于把调用进程加入到该字段指定的多播组,如果设置为0,表示调用者不加入任何多播组:
1.structsockaddr_nllocal;
2.
3.memset(&local,0,sizeof(local));
4.local.nl_family=AF_NETLINK;
5.local.nl_pid=getpid(); /*设置pid为自己的pid值*/
6.local.nl_groups=0;
7./*绑定套接字*/
8.if(bind(skfd,(structsockaddr*)&local,sizeof(local))!
=0)
9.{
10.printf("bind()error\n");
11. return-1;
12.}
复制代码
用户空间可以调用send函数簇向内核发送消息,如sendto、sendmsg等,同样地,也可以使用structsockaddr_nl来描述一个对端地址,以待send函数来调用,与本地地址稍不同的是,因为对端为内核,所以nl_pid成员需要设置为0:
1.structsockaddr_nlkpeer;
2.memset(&kpeer,0,sizeof(kpeer));
3.kpeer.nl_family=AF_NETLINK;
4.kpeer.nl_pid=0;
5.kpeer.nl_groups=0;
复制代码
另一个问题就是发内核发送的消息的组成,使用我们发送一个IP网络数据包的话,则数据包结构为“IP包头+IP数据”,同样地,netlink的消息结构是“netlink消息头部+数据”。
Netlink消息头部使用structnlmsghdr结构来描述:
1.structnlmsghdr
2.{
3. __u32nlmsg_len; /*Lengthofmessage*/
4. __u16nlmsg_type; /*Messagetype*/
5. __u16nlmsg_flags;/*Additionalflags*/
6. __u32nlmsg_seq; /*Sequencenumber*/
7. __u32nlmsg_pid; /*SendingprocessPID*/
8.};
复制代码
字段nlmsg_len指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,一般地,我们使用netlink提供的宏NLMSG_LENGTH来计算这个长度,仅需向NLMSG_LENGTH宏提供要发送的数据的长度,它会自动计算对齐后的总长度:
1./*计算包含报头的数据报长度*/
2.#defineNLMSG_LENGTH(len)((len)+NLMSG_ALIGN(sizeof(structnlmsghdr)))
3./*字节对齐*/
4.#defineNLMSG_ALIGN(len)(((len)+NLMSG_ALIGNTO-1)&~(NLMSG_ALIGNTO-1))
复制代码
后面还可以看到很多netlink提供的宏,这些宏可以为我们编写netlink宏提供很大的方便。
字段nlmsg_type用于应用内部定义消息的类型,它对netlink内核实现是透明的,因此大部分情况下设置为0,字段nlmsg_flags用于设置消息标志,对于一般的使用,用户把它设置为0就可以,只是一些高级应用(如netfilter和路由daemon需要它进行一些复杂的操作),字段nlmsg_seq和nlmsg_pid用于应用追踪消息,前者表示顺序号,后者为消息来源进程ID。
1.structmsg_to_kernel /*自定义消息首部,它仅包含了netlink的消息首部*/
2.{
3. structnlmsghdrhdr;
4.};
5.
6.structmsg_to_kernelmessage;
7.memset(&message,0,sizeof(message));
8.message.hdr.nlmsg_len=NLMSG_LENGTH(0); /*计算消息,因为这里只是发送一个请求消息,没有多余的数据,所以,数据长度为0*/
9.message.hdr.nlmsg_flags=0;
10.message.hdr.nlmsg_type=IMP2_U_PID; /*设置自定义消息类型*/
11.message.hdr.nlmsg_pid=local.nl_pid; /*设置发送者的PID*/
12.
13.这样,有了本地地址、对端地址和发送的数据,就可以调用发送函数将消息发送给内核了:
14. /*发送一个请求*/
15. sendto(skfd,&message,message.hdr.nlmsg_len,0,
16. (structsockaddr*)&kpeer,sizeof(kpeer));
复制代码
当发送完请求后,就可以调用recv函数簇从内核接收数据了,接收到的数据包含了netlink消息首部和要传输的数据:
1./*接收的数据包含了netlink消息首部和自定义数据结构*/
2.structu_packet_info
3.{
4. structnlmsghdrhdr;
5. structpacket_infoicmp_info;
6.};
7.structu_packet_infoinfo;
8.while
(1)
9.{
10. kpeerlen=sizeof(structsockaddr_nl);
11. /*接收内核空间返回的数据*/
12. rcvlen=recvfrom(skfd,&info,sizeof(structu_packet_info),
13. 0,(structsockaddr*)&kpeer,&kpeerlen);
14.
15. /*处理接收到的数据*/
16.……
17.}
复制代码
同样地,函数close用于关闭打开的netlinksocket。
程序中,因为程序一直循环接收处理内核的消息,需要收到用户的关闭信号才会退出,所以关闭套接字的工作放在了自定义的信号函数sig_int中处理:
1./*这个信号函数,处理一些程序退出时的动作*/
2.staticvoidsig_int(intsigno)
3.{
4. structsockaddr_nlkpeer;
5. structmsg_to_kernelmessage;
6.
7. memset(&kpeer,0,sizeof(kpeer));
8. kpeer.nl_family=AF_NETLINK;
9. kpeer.nl_pid =0;
10. kpeer.nl_groups=0;
11.
12. memset(&message,0,sizeof(message));
13. message.hdr.nlmsg_len=NLMSG_LENGTH(0);
14. message.hdr.nlmsg_flags=0;
15. message.hdr.nlmsg_type=IMP2_CLOSE;
16. message.hdr.nlmsg_pid=getpid();
17.
18. /*向内核发送一个消息,由nlmsg_type表明,应用程序将关闭*/
19. sendto(skfd,&message,message.hdr.nlmsg_len,0,(structsockaddr*)(&kpeer), sizeof(kpeer));
20.
21. close(skfd);
22. exit(0);
23.}
复制代码
这个结束函数中,向内核发送一个“我已经退出了”的消息,然后调用close函数关闭netlink套接字,退出程序。
内核空间
与应用程序内核,内核空间也主要完成三件工作:
n 创建netlink套接字
n 接收处理用户空间发送的数据
n 发送数据至用户空间
API函数netlink_kernel_create用于创建一个netlinksocket,同时,注册一个回调函数,用于接收处理用户空间的消息:
1.structsock*
2.netlink_kernel_create(intunit,void(*input)(structsock*sk,intlen));
复制代码
参数unit表示netlink协议类型,如NL_IMP2,参数input则为内核模块定义的netlink消息处理函数,当有消息到达这个netlinksocket时,该input函数指针就会被引用。
函数指针input的参数sk实际上就是函数netlink_kernel_create返回的structsock指针,sock实际是socket的一个内核表示数据结构,用户态应用创建的socket在内核中也会有一个structsock结构来表示。
1.staticint__initinit(void)
2.{
3. rwlock_init(&user_proc.lock); /*初始化读写锁*/
4.
5. /*创建一个netlinksocket,协议类型是自定义的ML_IMP2,kernel_reveive为接受处理函数*/
6. nlfd=netlink_kernel_create(NL_IMP2,kernel_receive);
7. if(!
nlfd) /*创建失败*/
8. {
9. printk("cannotcreateanetlinksocket\n");
10. return-1;
11. }
12.
13. /*注册一个Netfilter钩子*/
14. returnnf_register_hook(&imp2_ops);
15.}
16.
17.
18.module_init(init);
复制代码
用户空间向内核发送了两种自定义消息类型:
IMP2_U_PID和IMP2_CLOSE,分别是请求和关闭。
kernel_receive函数分别处理这两种消息:
1.DECLARE_MUTEX(receive_sem); /*初始化信号量*/
2.staticvoidkernel_receive(structsock*sk,intlen)
3.{
4. do
5. {
6. structsk_buff*skb;
7. if(down_trylock(&receive_sem)) /*获取信号量*/
8. return;
9. /*从接收队列中取得skb,然后进行一些基本的长度的合法性校验*/
10. while((skb=skb_dequeue(&sk->receive_queue))!
=NULL)
11. {
12. {
13. structnlmsghdr*nlh=NULL;
14.
15. if(skb->len>=sizeof(structnlmsghdr))
16. {
17. /*获取数据中的nlmsghdr结构的报头*/
18. nlh=(structnlmsghdr*)skb->data;
19. if((nlh->nlmsg_len>=sizeof(structnlmsghdr))
20. &&(skb->len>=nlh->nlmsg_len))
21. {
22. /*长度的全法性校验完成后,处理应用程序自定义消息类型,主要是对用户PID的保存,即为内核保存“把消息发送给谁”*/
23. if(nlh->nlmsg_type==IMP2_U_PID) /*请求*/
24. {
25. write_lock_bh(&user_proc.pid);
26. user_proc.pid=nlh->nlmsg_pid;
27. write_unlock_bh(&user_proc.pid);
28. }
29. elseif(nlh->nlmsg_type==IMP2_CLOSE) /*应用程序关闭*/
30. {
31. write_lock_bh(&user_proc.pid);
32.
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux 用户态与内核态的交互 用户 内核 交互