红联Linux门户
Linux帮助

Linux用户态与内核态的交互 (2)

发布时间:2006-10-16 00:49:11来源:红联作者:橙丢
同样地,函数close用于关闭打开的netlink socket。程序中,因为程序一直循环接收处理内核的消息,需要收到用户的关闭信号才会退出,所以关闭套接字的工作放在了自定义的信号函数sig_int中处理:[table=400][tr][td]/*这个信号函数,处理一些程序退出时的动作*/static void sig_int(int signo){ struct sockaddr_nl kpeer; struct msg_to_kernel message; memset(&kpeer, 0, sizeof(kpeer)); kpeer.nl_family = AF_NETLINK; kpeer.nl_pid = 0; kpeer.nl_groups = 0; memset(&message, 0, sizeof(message)); message.hdr.nlmsg_len = NLMSG_LENGTH(0); message.hdr.nlmsg_flags = 0; message.hdr.nlmsg_type = IMP2_CLOSE; message.hdr.nlmsg_pid = getpid();/*向内核发送一个消息,由nlmsg_type表明,应用程序将关闭*/sendto(skfd, &message, message.hdr.nlmsg_len, 0,(struct sockaddr *)(&kpeer), sizeof(kpeer)); close(skfd); exit(0);}[/td][/tr][/table]这个结束函数中,向内核发送一个“我已经退出了”的消息,然后调用close函数关闭netlink套接字,退出程序。 内核空间 与应用程序内核,内核空间也主要完成三件工作: n 创建netlink套接字 n 接收处理用户空间发送的数据 n 发送数据至用户空间 API函数netlink_kernel_create用于创建一个netlink socket,同时,注册一个回调函数,用于接收处理用户空间的消息:[table=400][tr][td]struct sock *netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));[/td][/tr][/table]参数unit表示netlink协议类型,如NL_IMP2,参数input则为内核模块定义的netlink消息处理函数,当有消息到达这个netlink socket时,该input函数指针就会被引用。函数指针input的参数sk实际上就是函数netlink_kernel_create返回的struct sock指针,sock实际是socket的一个内核表示数据结构,用户态应用创建的socket在内核中也会有一个struct sock结构来表示。[table=400][tr][td]static int __init init(void){ rwlock_init(&user_proc.lock); /*初始化读写锁*/ /*创建一个netlink socket,协议类型是自定义的ML_IMP2,kernel_reveive为接受处理函数*/ nlfd = netlink_kernel_create(NL_IMP2, kernel_receive); if(!nlfd) /*创建失败*/ { printk("can not create a netlink socket\n"); return -1; } /*注册一个Netfilter 钩子*/ return nf_register_hook(&imp2_ops);}[/td][/tr][/table]module_init(init); 用户空间向内核发送了两种自定义消息类型:IMP2_U_PID和IMP2_CLOSE, 分别是请求和关闭。kernel_receive 函数分别处理这两种消息:[table=400][tr][td]DECLARE_MUTEX(receive_sem); /*初始化信号量*/static void kernel_receive(struct sock *sk, int len){do{struct sk_buff *skb;if(down_trylock(&receive_sem)) /*获取信号量*/return;/*从接收队列中取得skb,然后进行一些基本的长度的合法性校验*/while((skb = skb_dequeue(&sk->receive_queue)) != NULL){{struct nlmsghdr *nlh = NULL;if(skb->len >= sizeof(struct nlmsghdr)){/*获取数据中的nlmsghdr 结构的报头*/nlh = (struct nlmsghdr *)skb->data;if((nlh->nlmsg_len >= sizeof(struct nlmsghdr))&& (skb->len >= nlh->nlmsg_len)){/*长度的全法性校验完成后,处理应用程序自定义消息类型,主要是对用户PID的保存,即为内核保存“把消息发送给谁”*/if(nlh->nlmsg_type == IMP2_U_PID) /*请求*/{write_lock_bh(&user_proc.pid);user_proc.pid = nlh->nlmsg_pid;write_unlock_bh(&user_proc.pid);}else if(nlh->nlmsg_type == IMP2_CLOSE) /*应用程序关闭*/{write_lock_bh(&user_proc.pid);if(nlh->nlmsg_pid == user_proc.pid)user_proc.pid = 0;write_unlock_bh(&user_proc.pid);}}}}kfree_skb(skb); }up(&receive_sem); /*返回信号量*/}while(nlfd && nlfd->receive_queue.qlen);}[/td][/tr][/table]因为内核模块可能同时被多个进程同时调用,所以函数中使用了信号量和锁来进行互斥。skb =skb_dequeue(&sk->receive_queue)用于取得socket sk的接收队列上的消息,返回为一个struct sk_buff的结构,skb->data指向实际的netlink消息。 程序中注册了一个Netfilter钩子,钩子函数是get_icmp,它截获ICMP数据包,然后调用send_to_user函数将数据发送给应用空间进程。发送的数据是info结构变量,它是struct packet_info结构,这个结构包含了来源/目的地址两个成员。Netfilter Hook不是本文描述的重点,略过。 send_to_user 用于将数据发送给用户空间进程,发送调用的是API函数netlink_unicast 完成的:[table=400][tr][td]int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);[/td][/tr][/table]参数sk为函数netlink_kernel_create()返回的套接字,参数skb存放待发送的消息,它的data字段指向要发送的netlink消息结构,而skb的控制块保存了消息的地址信息, 参数pid为接收消息进程pid,参数nonblock表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠。 向用户空间进程发送的消息包含三个部份:netlink 消息头部、数据部份和控制字段,控制字段包含了内核发送netlink消息时,需要设置的目标地址与源地址,内核中消息是通过sk_buff来管理的, linux/netlink.h中定义了NETLINK_CB宏来方便消息的地址设置:[table=400][tr][td]#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))例如: NETLINK_CB(skb).pid = 0;NETLINK_CB(skb).dst_pid = 0;NETLINK_CB(skb).dst_group = 1;
[/td][/tr][/table]字段pid表示消息发送者进程ID,也即源地址,对于内核,它为 0, dst_pid 表示消息接收者进程 ID,也即目标地址,如果目标为组或内核,它设置为 0,否则 dst_group 表示目标组地址,如果它目标为某一进程或内核,dst_group 应当设置为 0。[table=400][tr][td]static int send_to_user(struct packet_info *info){int ret;int size;unsigned char *old_tail;struct sk_buff *skb;struct nlmsghdr *nlh;struct packet_info *packet;/*计算消息总长:消息首部加上数据加度*/size = NLMSG_SPACE(sizeof(*info));/*分配一个新的套接字缓存*/skb = alloc_skb(size, GFP_ATOMIC);old_tail = skb->tail;/*初始化一个netlink消息首部*/nlh = NLMSG_PUT(skb, 0, 0, IMP2_K_MSG, size-sizeof(*nlh));/*跳过消息首部,指向数据区*/packet = NLMSG_DATA(nlh);/*初始化数据区*/memset(packet, 0, sizeof(struct packet_info));/*填充待发送的数据*/packet->src = info->src;packet->dest = info->dest;/*计算skb两次长度之差,即netlink的长度总和*/nlh->nlmsg_len = skb->tail - old_tail;/*设置控制字段*/NETLINK_CB(skb).dst_groups = 0;/*发送数据*/read_lock_bh(&user_proc.lock);ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT);read_unlock_bh(&user_proc.lock);}
[/td][/tr][/table]函数初始化netlink 消息首部,填充数据区,然后设置控制字段,这三部份都包含在skb_buff中,最后调用netlink_unicast函数把数据发送出去。 函数中调用了netlink的一个重要的宏NLMSG_PUT,它用于初始化netlink 消息首部:[table=400][tr][td]#define NLMSG_PUT(skb, pid, seq, type, len) \({ if (skb_tailroom(skb) < (int)NLMSG_SPACE(len)) goto nlmsg_failure; \ __nlmsg_put(skb, pid, seq, type, len); })static __inline__ struct nlmsghdr *__nlmsg_put(struct sk_buff *skb, u32 pid, u32 seq, int type, int len){ struct nlmsghdr *nlh; int size = NLMSG_LENGTH(len); nlh = (struct nlmsghdr*)skb_put(skb, NLMSG_ALIGN(size)); nlh->nlmsg_type = type; nlh->nlmsg_len = size; nlh->nlmsg_flags = 0; nlh->nlmsg_pid = pid; nlh->nlmsg_seq = seq; return nlh;}[/td][/tr][/table]这个宏一个需要注意的地方是调用了nlmsg_failure标签,所以在程序中应该定义这个标签。 在内核中使用函数sock_release来释放函数netlink_kernel_create()创建的netlink socket: void sock_release(struct socket * sock);程序在退出模块中释放netlink sockets和netfilter hook:[table=400][tr][td]static void __exit fini(void){ if(nlfd) { sock_release(nlfd->socket); /*释放netlink socket*/ } nf_unregister_hook(&imp2_ops); /*撤锁netfilter 钩子*/}
[/td]
[/tr][/table]
文章评论

共有 0 条评论