Linux使用者態與核心態的互動 (2)(轉)

ba發表於2007-08-16
Linux使用者態與核心態的互動 (2)(轉)[@more@]同樣地,函式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 "); 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]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-961218/,如需轉載,請註明出處,否則將追究法律責任。

相關文章