Linux 系統核心空間與使用者空間通訊的實現與分析

來源:互聯網
上載者:User

http://my.chinaunix.net/space.php?uid=20382483&do=blog&id=321047

多數的 Linux 核心態程式都需要和使用者空間的進程交換資料,但 Linux 核心態無法對傳統的 Linux 進程間同步和通訊的方法提供足夠的支援。本文總結並比較了幾種核心態與使用者態進程通訊的實現方法,並推薦使用 netlink 通訊端實現中斷環境與使用者態進程通訊。

1 引言

Linux 是一個源碼開放的作業系統,無論是普通使用者還是企業使用者都可以編寫自己的核心代碼,再加上對標準核心的裁剪從而製作出適合自己的作業系統。目前有很多中低 端使用者使用的網路裝置的作業系統是從標準 Linux 改進而來的,這也說明了有越來越多的人正在加入到 Linux 核心開發團體中。

一 個或多個核心模組的實現並不能滿足一般 Linux 系統軟體的需要,因為核心的局限性太大,如不能在終端上列印,不能做大延時的處理等等。當我們需要做這些的時候,就需要將在核心態採集到的資料傳送到使用者 態的一個或多個進程中進行處理。這樣,核心態與使用者空間進程通訊的方法就顯得尤為重要。在 Linux 的核心發行版本中沒有對該類通訊方法的詳細介紹,也沒有其他文章對此進行總結,所以本文將列舉幾種核心態與使用者態進程通訊的方法並詳細分析它們的實現和適 用環境。

2 Linux 核心模組的運行環境與傳統處理序間通訊

在一台運行 Linux 的電腦中,CPU 在任何時候只會有如下四種狀態:

【1】 在處理一個硬中斷。

【2】 在處理一個非強制中斷,如 softirq、tasklet 和 bh。

【3】 運行於核心態,但有進程上下文,即與一個進程相關。

【4】 運行一個使用者態進程。

其中,【1】、【2】和【3】是運行於核心空間的,而【4】是在使用者空間。其中除了【4】,其他狀態只可以被在其之上的狀態搶佔。比如,非強制中斷只可以被硬中斷搶佔。

Linux 核心模組是一段可以動態在核心裝載和卸載的代碼,裝載進核心的代碼便立即在核心中工作起來。Linux 核心代碼的運行環境有三種:使用者上下文環境、硬中斷環境和非強制中斷環境。但三種環境的局限性分兩種,因為非強制中斷環境只是硬中斷環境的延續。比較如表【1】。

表【1】

核心態環境

介紹

局限性

使用者上下文

核心態代碼的運行與一使用者空間進程相關,如系統調用中代碼的運行環境。

不可直接將本地變數傳遞給使用者態的記憶體區,因為核心態和使用者態的記憶體映射機制不同。

硬中斷和非強制中斷環境

硬中斷或非強制中斷過程中代碼的運行環境,如 IP 資料報的接收代碼的運行環境,網路裝置的驅動程式等。

不可直接向使用者態記憶體區傳遞資料;
代碼在運行過程中不可阻塞。

Linux 傳統的處理序間通訊有很多,如各類管道、訊息佇列、記憶體共用、訊號量等等。但它們都無法介於核心態與使用者態使用,原因如表【2】。

表【2】

通訊方法

無法介於核心態與使用者態的原因

管道(不包括具名管道)

局限於父子進程間的通訊。

訊息佇列

在硬、非強制中斷中無法無阻塞地接收資料。

訊號量

無法介於核心態和使用者態使用。

記憶體共用

需要訊號量輔助,而訊號量又無法使用。

通訊端

在硬、非強制中斷中無法無阻塞地接收資料。

3 Linux核心態與使用者態進程通訊方法的提出與實現

3.1 使用者上下文環境

運 行在使用者上下文環境中的代碼是可以阻塞的,這樣,便可以使用訊息佇列和 UNIX 域通訊端來實現核心態與使用者態的通訊。但這些方法的資料轉送效率較低,Linux 核心提供 copy_from_user()/copy_to_user() 函數來實現核心態與使用者態資料的拷貝,但這兩個函數會引發阻塞,所以不能用在硬、非強制中斷中。一般將這兩個特殊拷貝函數用在類似於系統調用一類的函數中,此 類函數在使用中往往"穿梭"於核心態與使用者態。此類方法的工作原理路【1】。

圖【1

其中相關的系統調用是需要使用者自行編寫並載入核心。 imp1.tar.gz是 一個樣本,核心模組註冊了一組設定通訊端選項的函數使得使用者空間進程可以調用此組函數對核心態資料進行讀寫。源碼包含三個檔案,imp1.h 是通用標頭檔,定義了使用者態和核心態都要用到的宏。imp1_k.c 是核心模組的原始碼。imp1_u.c 是使用者態進程的原始碼。整個樣本示範了由一個使用者態進程向使用者上下文環境發送一個字串,內容為"a message from userspace\n"。然後再由使用者上下文環境向使用者態進程發送一個字串,內容為"a message from kernel\n"。

3.2 硬、非強制中斷環境

比起使用者上下文環境,硬中斷和非強制中斷環境與使用者態進程無絲毫關係,而且運行過程不能阻塞。

321 使用一般處理序間通訊的方法

我 們無法直接使用傳統的處理序間通訊的方法實現。但硬、非強制中斷中也有一套同步機制--自旋鎖(spinlock),可以通過自旋鎖來實現中斷環境與中斷環境, 中斷環境與核心線程的同步,而核心線程是運行在有進程上下文環境中的,這樣便可以在核心線程中使用通訊端或訊息佇列來取得使用者空間的資料,然後再將資料通 過臨界區傳遞給中斷過程。基本思路【2】。

圖【2

因 為中斷過程不可能無休止地等待使用者態進程發送資料,所以要通過一個核心線程來接收使用者空間的資料,再通過臨界區傳給中斷過程。中斷過程向使用者空間的資料發 送必須是無阻塞的。這樣的通訊模型並不令人滿意,因為核心線程是和其他使用者態進程競爭CPU接收資料的,效率很低,這樣中斷過程便不能即時地接收來自使用者 空間的資料。

322 netlink 通訊端

在 Linux 2.4 版以後版本的核心中,幾乎全部的中斷過程與使用者態進程的通訊都是使用 netlink 通訊端實現的,同時還使用 netlink 實現了 ip queue 工具,但 ip queue 的使用有其局限性,不能自由地用於各種中斷過程。核心的協助文檔和其他一些 Linux 相關文章都沒有對 netlink 通訊端在中斷過程和使用者空間通訊的應用上作詳細的說明,使得很多使用者對此只有一個模糊的概念。

netlink 通訊端的通訊依據是一個對應於進程的標識,一般定為該進程的 ID。當通訊的一端處於中斷過程時,該標識為 0。當使用 netlink 通訊端進行通訊,通訊的雙方都是使用者態進程,則使用方法類似於訊息佇列。但通訊雙方有一端是中斷過程,使用方法則不同。netlink 通訊端的最大特點是對中斷過程的支援,它在核心空間接收使用者空間資料時不再需要使用者自行啟動一個核心線程,而是通過另一個非強制中斷調用使用者事先指定的接收函 數。工作原理【3】。

圖【3

很明顯,這裡使用了非強制中斷而不是核心線程來接收資料,這樣就可以保證資料接收的即時性。

當 netlink 通訊端用於核心空間與使用者空間的通訊時,在使用者空間的建立方法和一般通訊端使用類似,但核心空間的建立方法則不同。圖【4】是 netlink 通訊端實現此類通訊時建立的過程。

圖【4

以下舉一個 netlink 通訊端的應用樣本。樣本實現了從 netfilter 的 NF_IP_PRE_ROUTING 點截獲的 ICMP 資料報,在將資料報的相關資訊傳遞到一個使用者態進程,由使用者態進程將資訊列印在終端上。源碼在檔案 imp2.tar.gz中。 核心模組代碼(分段詳解):

(一)模組初始化與卸載

static struct sock *nlfd;
struct
{
__u32 pid;
rwlock_t lock;
}user_proc;
/*掛接在 netfilter 架構的 NF_IP_PRE_ROUTING 點上的函數為 get_icmp()*/
static struct nf_hook_ops imp2_ops =
{
.hook = get_icmp, /*netfilter 鉤子函數*/
.pf = PF_INET,
.hooknum = NF_IP_PRE_ROUTING,
.priority = NF_IP_PRI_FILTER -1,
};
static int __init init(void)
{
rwlock_init(&user_proc.lock);
/*在核心建立一個 netlink socket,並註明由 kernel_recieve() 函數接收資料
這裡協議 NL_IMP2 是自定的*/
nlfd = netlink_kernel_create(NL_IMP2, kernel_receive);
if(!nlfd)
{
printk("can not create a netlink socket\n");
return -1;
}
/*向 netfilter 的 NF_IP_PRE_ROUTING 點掛接函數*/
return nf_register_hook(&imp2_ops);
}
static void __exit fini(void)
{
if(nlfd)
{
sock_release(nlfd->socket);
}
nf_unregister_hook(&imp2_ops);
}
module_init(init);
module_exit(fini);

其實片斷(一)的工作很簡單,模組載入階段先在核心空間建立一個 netlink 通訊端,再將一個函數掛接在 netfilter 架構的 NF_IP_PRE_ROUTING 鉤子點上。卸載時釋放通訊端所佔的資源並登出之前在 netfilter 上掛接的函數。

(二)接收使用者空間的資料

DECLARE_MUTEX(receive_sem);
01: static void kernel_receive(struct sock *sk, int len)
02: {
03: do
04: {
05: struct sk_buff *skb;
06: if(down_trylock(&receive_sem))
07: return;
08:
09: while((skb = skb_dequeue(&sk-<receive_queue)) != NULL)
10: {
11: {
12: struct nlmsghdr *nlh = NULL;
13: if(skb-<len <= sizeof(struct nlmsghdr))
14: {
15: nlh = (struct nlmsghdr *)skb-<data;
16: if((nlh-<nlmsg_len <= sizeof(struct nlmsghdr))
17: && (skb-<len <= nlh-<nlmsg_len))
18: {
19: if(nlh-<nlmsg_type == IMP2_U_PID)
20: {
21: write_lock_bh(&user_proc.pid);
22: user_proc.pid = nlh-<nlmsg_pid;
23: write_unlock_bh(&user_proc.pid);
24: }
25: else if(nlh-<nlmsg_type == IMP2_CLOSE)
26: {
27: write_lock_bh(&user_proc.pid);
28: if(nlh-<nlmsg_pid == user_proc.pid) user_proc.pid = 0;
29: write_unlock_bh(&user_proc.pid);
30: }
31: }
32: }
33: }
34: kfree_skb(skb);
35: }
36: up(&receive_sem);
37: }while(nlfd && nlfd-<receive_queue.qlen);
38: }

如果讀者看過 ip_queue.c 或 rtnetlink.c中的源碼會發現片斷(二)中的 03~18 和 31~38 是 netlink socket 在核心空間接收資料的架構。在架構中主要是從通訊端緩衝中取出全部的資料,然後分析是不是合法的資料報,合法的 netlink 資料報必須有nlmsghdr 結構的前序。在這裡筆者使用了自己定義的訊息類型:IMP2_U_PID(訊息為使用者空間進程的ID),IMP2_CLOSE(使用者空間進程關閉)。因為 考慮到 SMP,所以在這裡使用了讀寫鎖來避免不同 CPU 訪問臨界區的問題。kernel_receive() 函數的運行在非強制中斷環境。

(三)截獲 IP 資料報

static unsigned int get_icmp(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct iphdr *iph = (*pskb)->nh.iph;
struct packet_info info;
if(iph->protocol == IPPROTO_ICMP) /*若傳輸層協議為 ICMP*/
{
read_lock_bh(&user_proc.lock);
if(user_proc.pid != 0)
{
read_unlock_bh(&user_proc.lock);
info.src = iph->saddr; /*記錄源地址*/
info.dest = iph->daddr; /*記錄目的地址*/
send_to_user(&info); /*發送資料*/
}
else
read_unlock_bh(&user_proc.lock);
}
return NF_ACCEPT;
}

(四)發送資料

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;
/*填寫資料報相關資訊*/
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;
/*計算經過位元組對其後的資料實際長度*/
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);
return ret;
nlmsg_failure: /*若發送失敗,則撤銷通訊端緩衝*/
if(skb)
kfree_skb(skb);
return -1;
}

片斷(四)中所使用的宏參考如下:

/*位元組對齊*/
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
/*計算包含前序的資料報長度*/
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/*位元組對齊後的資料報長度*/
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
/*填寫相關前序資訊,這裡使用了nlmsg_failure標籤,所以在程式中要定義*/
#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;
}
/*跳過前序取實際資料*/
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
/*取 netlink 控制欄位*/
#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))

運行樣本時,先編譯 imp2_k.c 模組,然後使用 insmod 將模組載入入核心。再運行編譯好的 imp2_u 命令,此時就會顯示出本機當前接收的 ICMP 資料報的源地址和目的地址。使用者可以使用 Ctrl+C 來終止使用者空間的進程,再次啟動也不會帶來問題。

4 總結

本文從核心態代碼的不同運行環境來實現不同方法的核心空間與使用者空間的通訊,並分析了它們的實際效果。最後推薦使用 netlink 通訊端實現中斷環境與使用者態進程通訊,因為 netlink 通訊端是專為此類通訊定製的。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.