Linux用户态与内核态的交互 (2)
时间:2006-10-15 来源:迷信的兔子
同样地,函数close用于关闭打开的netlink socket。程序中,因为程序一直循环接收处理内核的消息,需要收到用户的关闭信号才会退出,所以关闭套接字的工作放在了自定义的信号函数sig_int中处理:
/*这个信号函数,处理一些程序退出时的动作*/ 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); } |
这个结束函数中,向内核发送一个“我已经退出了”的消息,然后调用close函数关闭netlink套接字,退出程序。
内核空间
与应用程序内核,内核空间也主要完成三件工作:
n 创建netlink套接字
n 接收处理用户空间发送的数据
n 发送数据至用户空间
API函数netlink_kernel_create用于创建一个netlink socket,同时,注册一个回调函数,用于接收处理用户空间的消息:
struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len)); |
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); } |
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); } |
因为内核模块可能同时被多个进程同时调用,所以函数中使用了信号量和锁来进行互斥。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 完成的:
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock); |
参数sk为函数netlink_kernel_create()返回的套接字,参数skb存放待发送的消息,它的data字段指向要发送的netlink消息结构,而skb的控制块保存了消息的地址信息, 参数pid为接收消息进程pid,参数nonblock表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠。
向用户空间进程发送的消息包含三个部份:netlink 消息头部、数据部份和控制字段,控制字段包含了内核发送netlink消息时,需要设置的目标地址与源地址,内核中消息是通过sk_buff来管理的, linux/netlink.h中定义了NETLINK_CB宏来方便消息的地址设置:
#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb)) |
字段pid表示消息发送者进程ID,也即源地址,对于内核,它为 0, dst_pid 表示消息接收者进程 ID,也即目标地址,如果目标为组或内核,它设置为 0,否则 dst_group 表示目标组地址,如果它目标为某一进程或内核,dst_group 应当设置为 0。
static int send_to_user(struct packet_info *info) |
函数初始化netlink 消息首部,填充数据区,然后设置控制字段,这三部份都包含在skb_buff中,最后调用netlink_unicast函数把数据发送出去。
函数中调用了netlink的一个重要的宏NLMSG_PUT,它用于初始化netlink 消息首部:
#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; } |
这个宏一个需要注意的地方是调用了nlmsg_failure标签,所以在程序中应该定义这个标签。
在内核中使用函数sock_release来释放函数netlink_kernel_create()创建的netlink socket: void sock_release(struct socket * sock);程序在退出模块中释放netlink sockets和netfilter hook:
static void __exit fini(void) |