文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>Linux用户态与内核态的交互 (2)

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));
参数unit表示netlink协议类型,如NL_IMP2,参数input则为内核模块定义的netlink消息处理函数,当有消息到达这个netlink socket时,该input函数指针就会被引用。函数指针input的参数sk实际上就是函数netlink_kernel_create返回的struct sock指针,sock实际是socket的一个内核表示数据结构,用户态应用创建的socket在内核中也会有一个struct sock结构来表示。
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); }
module_init(init); 用户空间向内核发送了两种自定义消息类型:IMP2_U_PID和IMP2_CLOSE, 分别是请求和关闭。kernel_receive 函数分别处理这两种消息:
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)) 


例如:
NETLINK_CB(skb).pid = 0;

NETLINK_CB(skb).dst_pid = 0;

NETLINK_CB(skb).dst_group = 1;

字段pid表示消息发送者进程ID,也即源地址,对于内核,它为 0, dst_pid 表示消息接收者进程 ID,也即目标地址,如果目标为组或内核,它设置为 0,否则 dst_group 表示目标组地址,如果它目标为某一进程或内核,dst_group 应当设置为 0。

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);





}

 

函数初始化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) 

{

if(nlfd)

{

sock_release(nlfd->socket); /*释放netlink socket*/

}

nf_unregister_hook(&imp2_ops); /*撤锁netfilter 钩子*/

}

           
相关阅读 更多 +
排行榜 更多 +
鱼群进化跑

鱼群进化跑

休闲益智 下载
收纳物语暑假作业

收纳物语暑假作业

休闲益智 下载
航海战纪

航海战纪

动作格斗 下载