? 關(guān)于Netlink多播機(jī)制的用法
? ? ? ? 在上一篇博文中我們所遇到的情況都是用戶空間作為消息進(jìn)程的發(fā)起者,Netlink還支持內(nèi)核作為消息的發(fā)送方的情況。這一般用于內(nèi)核主動(dòng)向用戶空間報(bào)告一些內(nèi)核狀態(tài),例如我們?cè)谟脩艨臻g看到的USB的熱插拔事件的通告就是這樣的應(yīng)用。
? ? ? ?先說一下我們的目標(biāo),內(nèi)核線程每個(gè)一秒鐘往一個(gè)多播組里發(fā)送一條消息,然后用戶空間所以加入了該組的進(jìn)程都會(huì)收到這樣的消息,并將消息內(nèi)容打印出來。
?? ? ? ?Netlink地址結(jié)構(gòu)體中的nl_groups是32位,也就是說每種Netlink協(xié)議最多支持32個(gè)多播組。如何理解這里所說的每種Netlink協(xié)議?在里預(yù)定義的如下協(xié)議都是Netlink協(xié)議簇的具體協(xié)議,還有我們添加的NETLINK_TEST也是一種Netlink協(xié)議。
點(diǎn)擊(此處)折疊或打開
#define NETLINK_ROUTE????????0????/*?Routing/device hook????????????????*/
#define NETLINK_UNUSED????????1????/*?Unused number????????????????*/
#define NETLINK_USERSOCK????2????/*?Reserved?for?user mode socket protocols ????*/
#define NETLINK_FIREWALL????3????/*?Firewalling hook????????????????*/
#define NETLINK_INET_DIAG????4????/*?INET socket monitoring????????????*/
#define NETLINK_NFLOG????????5????/*?netfilter/iptables ULOG?*/
#define NETLINK_XFRM????????6????/*?ipsec?*/
#define NETLINK_SELINUX????????7????/*?SELinux event notifications?*/
#define NETLINK_ISCSI????????8????/*?Open-iSCSI?*/
#define NETLINK_AUDIT????????9????/*?auditing?*/
#define NETLINK_FIB_LOOKUP????10????
#define NETLINK_CONNECTOR????11
#define NETLINK_NETFILTER????12????/*?netfilter subsystem?*/
#define NETLINK_IP6_FW????????13
#define NETLINK_DNRTMSG????????14????/*?DECnet routing messages?*/
#define NETLINK_KOBJECT_UEVENT????15????/*?Kernel messages?to?userspace?*/
#define NETLINK_GENERIC????????16
/*?leave room?for?NETLINK_DM?(DM Events)?*/
#define NETLINK_SCSITRANSPORT????18????/*?SCSI Transports?*/
#define NETLINK_ECRYPTFS????19
#define NETLINK_TEST 20 /* 用戶添加的自定義協(xié)議 */
在我們自己添加的NETLINK_TEST協(xié)議里,同樣地,最多允許我們?cè)O(shè)置32個(gè)多播組,每個(gè)多播組用1個(gè)比特表示,所以不同的多播組不可能出現(xiàn)重復(fù)。你可以根據(jù)自己的實(shí)際需求,決定哪個(gè)多播組是用來做什么的。用戶空間的進(jìn)程如果對(duì)某個(gè)多播組感興趣,那么它就加入到該組中,當(dāng)內(nèi)核空間的進(jìn)程往該組發(fā)送多播消息時(shí),所有已經(jīng)加入到該多播組的用戶進(jìn)程都會(huì)收到該消息。
? ? ? ?再回到我們Netlink地址結(jié)構(gòu)體里的nl_groups成員,它是多播組的地址掩碼,注意是掩碼不是多播組的組號(hào)。如何根據(jù)多播組號(hào)取得多播組號(hào)的掩碼呢?在af_netlink.c中有個(gè)函數(shù):
點(diǎn)擊(此處)折疊或打開
static u32 netlink_group_mask(u32 group)
{
return group???1?<(group?-?1)?:?0;
}
也就是說,在用戶空間的代碼里,如果我們要加入到多播組1,需要設(shè)置nl_groups設(shè)置為1;多播組2的掩碼為2;多播組3的掩碼為4,依次類推。為0表示我們不希望加入任何多播組。理解這一點(diǎn)很重要。所以我們可以在用戶空間也定義一個(gè)類似于netlink_group_mask()的功能函數(shù),完成從多播組號(hào)到多播組掩碼的轉(zhuǎn)換。最終用戶空間的代碼如下:
?
點(diǎn)擊(此處)折疊或打開
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#define MAX_PAYLOAD 1024?//?Netlink消息的最大載荷的長(zhǎng)度
unsigned int netlink_group_mask(unsigned int group)
{
return group ? 1 << (group - 1) : 0;
}
int?main(int?argc,?char*?argv[])
{
struct sockaddr_nl src_addr;
struct nlmsghdr?*nlh?=?NULL;
struct iovec iov;
struct msghdr msg;
int?sock_fd,?retval;
//?創(chuàng)建Socket
sock_fd?=?socket(PF_NETLINK,?SOCK_RAW,?NETLINK_TEST);
if(sock_fd?==?-1){
printf("error getting socket: %s",?strerror(errno));
return?-1;
}
memset(&src_addr,?0,?sizeof(src_addr));
src_addr.nl_family?=?PF_NETLINK;
src_addr.nl_pid = 0;?//?表示我們要從內(nèi)核接收多播消息。注意:該字段為0有雙重意義,另一個(gè)意義是表示我們發(fā)送的數(shù)據(jù)的目的地址是內(nèi)核。
src_addr.nl_groups?=?netlink_group_mask(atoi(argv[1]));?//?多播組的掩碼,組號(hào)來自我們執(zhí)行程序時(shí)輸入的第一個(gè)參數(shù)
//?因?yàn)槲覀円尤氲揭粋€(gè)多播組,所以必須調(diào)用bind()。
retval?=?bind(sock_fd,?(struct sockaddr*)&src_addr,?sizeof(src_addr));
if(retval?0){
printf("bind failed: %s",?strerror(errno));
close(sock_fd);
return?-1;
}
//?為接收Netlink消息申請(qǐng)存儲(chǔ)空間
nlh?=?(struct nlmsghdr?*)malloc(NLMSG_SPACE(MAX_PAYLOAD));
if(!nlh){
printf("malloc nlmsghdr error!\n");
close(sock_fd);
return?-1;
}
memset(nlh,?0,?NLMSG_SPACE(MAX_PAYLOAD));
iov.iov_base?=?(void?*)nlh;
iov.iov_len?=?NLMSG_SPACE(MAX_PAYLOAD);
memset(&msg,?0,?sizeof(msg));
msg.msg_iov?=?&iov;
msg.msg_iovlen?=?1;
//?從內(nèi)核接收消息
printf("waitinf for...\n");
recvmsg(sock_fd,?&msg,?0);
printf("Received message: %s \n",?NLMSG_DATA(nlh));
close(sock_fd);
return 0;
}
可以看到,用戶空間的程序基本沒什么變化,唯一需要格外注意的就是Netlink地址結(jié)構(gòu)體中的nl_groups的設(shè)置。由于對(duì)它的解釋很少,加之沒有有效的文檔,所以我也是一邊看源碼,一邊在網(wǎng)上搜集資料。有分析不當(dāng)之處,還請(qǐng)大家?guī)臀抑赋觥?br />
? ? ? ?內(nèi)核空間我們添加了內(nèi)核線程和內(nèi)核線程同步方法completion的使用。內(nèi)核空間修改后的代碼如下:
?
點(diǎn)擊(此處)折疊或打開
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include??
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Koorey King");
struct sock?*nl_sk?=?NULL;
static struct task_struct *mythread = NULL; //內(nèi)核線程對(duì)象
//向用戶空間發(fā)送消息的接口
void sendnlmsg(char?*message/*,int?dstPID*/)
{
struct sk_buff?*skb;
struct nlmsghdr?*nlh;
int?len?=?NLMSG_SPACE(MAX_MSGSIZE);
int?slen?=?0;
if(!message?||?!nl_sk){
return;
}
//?為新的 sk_buffer申請(qǐng)空間
skb?=?alloc_skb(len,?GFP_KERNEL);
if(!skb){
printk(KERN_ERR?"my_net_link: alloc_skb Error./n");
return;
}
slen?=?strlen(message)+1;
//用nlmsg_put()來設(shè)置netlink消息頭部
nlh?=?nlmsg_put(skb,?0,?0,?0,?MAX_MSGSIZE,?0);
//?設(shè)置Netlink的控制塊里的相關(guān)信息
NETLINK_CB(skb).pid?=?0;?//?消息發(fā)送者的id標(biāo)識(shí),如果是內(nèi)核發(fā)的則置0
NETLINK_CB(skb).dst_group = 5; //多播組號(hào)為5,但置成0好像也可以。
message[slen]?=?'\0';
memcpy(NLMSG_DATA(nlh),?message,?slen+1);
//通過netlink_unicast()將消息發(fā)送用戶空間由dstPID所指定了進(jìn)程號(hào)的進(jìn)程
//netlink_unicast(nl_sk,skb,dstPID,0);
netlink_broadcast(nl_sk, skb, 0,5, GFP_KERNEL);?//發(fā)送多播消息到多播組5,這里我故意沒有用1之類的“常見”值,目的就是為了證明我們上面提到的多播組號(hào)和多播組號(hào)掩碼之間的對(duì)應(yīng)關(guān)系
printk("send OK!\n");
return;
}
//每隔1秒鐘發(fā)送一條“I am from kernel!”消息,共發(fā)10個(gè)報(bào)文
static int sending_thread(void *data)
{
int i = 10;
struct completion cmpl;
while(i--){
init_completion(&cmpl);
wait_for_completion_timeout(&cmpl, 1 * HZ);
sendnlmsg("I am from kernel!");
}
printk("sending thread exited!");
return 0;
}
static?int?__init myinit_module()
{
printk("my netlink in\n");
nl_sk?=?netlink_kernel_create(NETLINK_TEST,0,NULL,THIS_MODULE);
if(!nl_sk){
printk(KERN_ERR?"my_net_link: create netlink socket error.\n");
return 1;
}
printk("my netlink: create netlink socket ok.\n");
mythread = kthread_run(sending_thread,NULL,"thread_sender");
return 0;
}
static void __exit mycleanup_module()
{
if(nl_sk?!=?NULL){
sock_release(nl_sk->sk_socket);
}
printk("my netlink out!\n");
}
module_init(myinit_module);
module_exit(mycleanup_module);
關(guān)于內(nèi)核中netlink_kernel_create(int unit, unsigned int groups,…)函數(shù)里的第二個(gè)參數(shù)指的是我們內(nèi)核進(jìn)程最多能處理的多播組的個(gè)數(shù),如果該值小于32,則默認(rèn)按32處理,所以在調(diào)用netlink_kernel_create()函數(shù)時(shí)可以不用糾結(jié)第二個(gè)參數(shù),一般將其置為0就可以了。
?
? ? ? ?在skbuff{}結(jié)構(gòu)體中,有個(gè)成員叫做"控制塊",源碼對(duì)它的解釋如下:
?
點(diǎn)擊(此處)折疊或打開
struct sk_buff?{
/*?These two members must be first.?*/
struct sk_buff????????*next;
struct sk_buff????????*prev;
… …
/*
*?This?is?the control buffer.?It?is?free?to?use?for?every
*?layer.?Please put your?private?variables there.?If?you
*?want?to?keep them across layers you have?to?do?a skb_clone()
*?first.?This?is?owned by whoever has the skb queued ATM.
*/
char????????????cb[48];
… …
}
當(dāng)內(nèi)核態(tài)的Netlink發(fā)送數(shù)據(jù)到用戶空間時(shí)一般需要填充skbuff的控制塊,填充的方式是通過強(qiáng)制類型轉(zhuǎn)換,將其轉(zhuǎn)換成struct netlink_skb_parms{}之后進(jìn)行填充賦值的:
?
點(diǎn)擊(此處)折疊或打開
struct netlink_skb_parms
{
struct ucred????????creds;????????/*?Skb credentials????*/
__u32????????????pid;
__u32????????????dst_group;
kernel_cap_t????????eff_cap;
__u32????????????loginuid;????/*?Login?(audit)?uid?*/
__u32????????????sid;????????/*?SELinux security id?*/
};
填充時(shí)的模板代碼如下:
?
點(diǎn)擊(此處)折疊或打開
NETLINK_CB(skb).pid=xx;
NETLINK_CB(skb).dst_group=xx;
這里要注意的是在Netlink協(xié)議簇里提到的skbuff的cb控制塊里保存的是屬于Netlink的私有信息。怎么講,就是Netlink會(huì)用該控制塊里的信息來完成它所提供的一些功能,只是完成Netlink功能所必需的一些私有數(shù)據(jù)。打個(gè)比方,以開車為例,開車的時(shí)候我們要做的就是打火、控制方向盤、適當(dāng)?shù)乜刂朴烷T和剎車,車就開動(dòng)了,這就是汽車提供給我們的“功能”。汽車的發(fā)動(dòng)機(jī),輪胎,傳動(dòng)軸,以及所用到的螺絲螺栓等都屬于它的“私有”數(shù)據(jù)cb。汽車要運(yùn)行起來這些東西是不可或缺的,但它們之間的協(xié)作和交互對(duì)用戶來說又是透明的。就好比我們Netlink的私有控制結(jié)構(gòu)struct netlink_skb_parms{}一樣。
? ? ? ?目前我們的例子中,將NETLINK_CB(skb).dst_group設(shè)置為相應(yīng)的多播組號(hào)和0效果都是一樣,用戶空間都可以收到該多播消息,原因還不是很清楚,還請(qǐng)Netlink的大蝦們幫我點(diǎn)撥點(diǎn)撥。
? ? ? ?編譯后重新運(yùn)行,最后的測(cè)試結(jié)果如下:
?
? ? ? ?注意,這里一定要先執(zhí)行insmod加載內(nèi)核模塊,然后再運(yùn)行用戶空間的程序。如果沒有加載mynlkern.ko而直接執(zhí)行./test 5在bind()系統(tǒng)調(diào)用時(shí)會(huì)報(bào)如下的錯(cuò)誤:
? ? ? ?bind failed: No such file or directory
? ? ? ?因?yàn)榫W(wǎng)上有寫文章在講老版本Netlink的多播時(shí)用法時(shí)先執(zhí)行了用戶空間的程序,然后才加載內(nèi)核模塊,現(xiàn)在(2.6.21)已經(jīng)行不通了,這一點(diǎn)請(qǐng)大家注意。
? ? ? ?小結(jié):通過這三篇博文我們對(duì)Netlink有了初步的認(rèn)識(shí),并且也可以開發(fā)基于Netlink的基本應(yīng)用程序。但這只是冰山一角,要想寫出高質(zhì)量、高效率的軟件模塊還有些差距,特別是對(duì)Netlink本質(zhì)的理解還需要提高一個(gè)層次,當(dāng)然這其中牽扯到內(nèi)核編程的很多基本功,如臨界資源的互斥、線程安全性保護(hù)、用Netlink傳遞大數(shù)據(jù)時(shí)的處理等等都是開發(fā)人員需要考慮的問題。
?
評(píng)論
查看更多