2014/12/16

miracast 與 chromecast 比較, 還有DLNA

你要知道其實在android裡面, google已經先透過wifi-direct搞出miracast囉
後來才誘發展出chromecast的方式, 而這兩種看起來很像但是品質提升了!
以protocol串流傳遞codec來看, miracast是h.264(即mp4的其中一種), 而chromecast所用的技術除了h.264之外也支援vp8 (處理時以vp8為優先選擇)
如果有興趣可以看這篇:
Performance Analysis of H.264/AVC, H.264/SVC, and VP8 over IEEE 802.11 Wireless Networks

另外, 通常開發miracast dongle的廠商, 會一併將DLNA功能作進去產品內
但這兩種是走不同方式傳資料…

miracast是直接對連, 每支手機的調教程度不一
DLNA是移動裝置要線連上dongle的區域網路, 缺點就是此時無法連到internet!
DLNA使用軟體BubbleUPnP iMediaShare 主要用iMediaShare

Miracast與DLNA的產品介紹:

chromecast 有不同模式, 類似Dlna 與類似miracast兩種
[類似Dlna]: 可用google play movie / play music 選擇推播至電視
[類似miracast]: 可透過快捷選單, enable chromecast然後建立連結至dongle即可
除了上述兩種, 另外還可以點播雲端的Youtube影片, 這個我覺得滿優的!

但是..
chromecast鏡射畫面若是用電腦chrome瀏覽器會delay滿明顯的,
若用android手機(好像android5.0之後才支援)也稍有頓頓的

2014/12/15

在Linux 下User Space(用戶空間)與Kernel Space(內核空間) 溝通與資料交換的 方式 有9種

引用: 燚楊 ( yang.y.yi@gmail.com ),計算機科學碩士 2006年2月16日Part1, Part2

第1 部分: 內核啟動參數、模塊參數與sysfs、sysctl、系統調用和netlink
本系列文章包括兩部分,它們文詳細地介紹了Linux系統下用戶空間與內核空間數據交換的九種方式,包括內核啟動參數、模塊參數與sysfs、sysctl、系統調用、netlink、procfs、seq_file、debugfs和relayfs,並給出具體的例子幫助讀者掌握這些技術的使用。它介紹了內核啟動參數、模塊參數與sysfs、sysctl、系統調用和netlink,並結合給出的例子程序詳細地說明了它們如何使用。

引言

一般地,在使用虛擬內存技術的多任務系統上,內核和應用有不同的地址空間,因此,在內核和應用之間以及在應用與應用之間進行數據交換需要專門的機制來實現,眾所周知,進程間通信(IPC)機制就是為實現應用與應用之間的數據交換而專門實現的,大部分讀者可能對進程間通信比較了解,但對應用與內核之間的數據交換機制可能了解甚少,本文將詳細介紹Linux 系統下內核與應用進行數據交換的各種方式,包括內核啟動參數、模塊參數與sysfs、sysctl、系統調用、netlink、procfs、seq_file、debugfs 和relayfs。

1、內核啟動參數

Linux 提供了一種通過bootloader 向其傳輸啟動參數的功能,內核開發者可以通過這種方式來向內核傳輸數據,從而控制內核啟動行為。
通常的使用​​方式是,定義一個分析參數的函數,而後使用內核提供的宏__setup把它註冊到內核中,該宏定義在 linux/init.h 中,因此要使用它必須包含該頭文件:

__setup("para_name=", parse_func)

para_name 為參數名,parse_func 為分析參數值的函數,它負責把該參數的值轉換成相應的內核變量的值並設置那個內核變量。
內核為整數參數值的分析提供了函數get_option 和get_options,前者用於分析參數值為一個整數的情況,而後者用於分析參數值為逗號分割的一系列整數的情況,對於參數值為字符串的情況,需要開發者自定義相應的分析函數。
在源代碼包中的內核程序kern-boot-params.c 說明了三種情況的使用。
該程序列舉了參數為一個整數、逗號分割的整數串以及字符串三種情況,讀者要想測試該程序,需要把該程序拷貝到要使用的內核的源碼目錄樹的一個目錄下,為了避免與內核其他部分混淆,作者建議在內核源碼樹的根目錄下創建一個新目錄,如examples,然後把該程序拷貝到examples 目錄下並重新命名為setup_example.c,並且為該目錄創建一個Makefile 文件:

obj-y = setup_example.o

Makefile 僅許這一行就足夠了,然後需要修改源碼樹的根目錄下的Makefile文件的一行,把下面行

core-y := usr/

修改為

core-y := usr/ examples/

注意:如果讀者創建的新目錄和重新命名的文件名與上面不同,需要修改上面所說Makefile 文件相應的位置。做完以上工作就可以按照內核構建步驟去構建新的內核,在構建好內核並設置好lilo或grub為該內核的啟動條目後,就可以啟動該內核,然後使用lilo或grub的編輯功能為該內核的啟動參數行增加如下參數串:

setup_example_int=1234 setup_example_int_array=100,200,300,400 setup_example_string=Thisisatest

當然,該參數串也可以直接寫入到lilo或grub的配置文件中對應於該新內核的內核命令行參數串中。讀者可以使用其它參數值來測試該功能。
下面是作者係統上使用上面參數行的輸出:

setup_example_int=1234
setup_example_int_array=100,200,300,400
setup_example_int_array includes 4 intergers
setup_example_string=Thisisatest

讀者可以使用

dmesg | grep setup

來查看該程序的輸出。

2、模塊參數與sysfs

內核子系統或設備驅動可以直接編譯到內核,也可以編譯成模塊,如果編譯到內核,可以使用前一節介紹的方法通過內核啟動參數來向它們傳遞參數,如果編譯成模塊,則可以通過命令行在插入模塊時傳遞參數,或者在運行時,通過sysfs來設置或讀取模塊數據。

Sysfs是一個基於內存的文件系統,實際上它基於ramfs,sysfs提供了一種把內核數據結構,它們的屬性以及屬性與數據結構的聯繫開放給用戶態的方式,它與kobject子系統緊密地結合在一起,因此內核開發者不需要直接使用它,而是內核的各個子系統使用它。用戶要想使用sysfs 讀取和設置內核參數,僅需裝載sysfs 就可以通過文件操作應用來讀取和設置內核通過sysfs 開放給用戶的各個參數:

$ mkdir -p /sysfs$ mount -t sysfs sysfs /sysfs

注意,不要把sysfs 和sysctl 混淆,sysctl 是內核的一些控制參數,其目的是方便用戶對內核的行為進行控制,而sysfs 僅僅是把內核的kobject 對象的層次關係與屬性開放給用戶查看,因此sysfs的絕大部分是只讀的,模塊作為一個kobject 也被出口到sysfs,模塊參數則是作為模塊屬性出口的,內核實現者為模塊的使用提供了更靈活的方式,允許用戶設置模塊參數在sysfs的可見性並允許用戶在編寫模塊時設置這些參數在sysfs 下的訪問權限,然後用戶就可以通過sysfs 來查看和設置模塊參數,從而使得用戶能在模塊運行時控制模塊行為。

對於模塊(module)而言,聲明為static 的變量都可以通過命令行來設置,但要想在sysfs下可見,必須通過宏module_param 來顯式聲明,該宏有三個參數,第一個為參數名,即已經定義的變量名,第二個參數則為變量類型,可用的類型有byte, short, ushort, int, uint, long, ulong, charp 和bool 或invbool,分別對應於c 類型char, short, unsigned short, int, unsigned int, long, unsigned long, char * 和int,用戶也可以自定義類型XXX(如果用戶自己定義了param_get_XXX,param_set_XXX 和param_check_XXX)。

該宏的第三個參數用於指定訪問權限,如果為0,該參數將不出現在sysfs 文件系統中,允許的訪問權限為S_IRUSR, S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH 和S_IWOTH 的組合,它們分別對應於用戶讀,用戶寫,用戶組讀,用戶組寫,其他用戶讀和其他用戶寫,因此用文件的訪問權限設置是一致的。

在源代碼包中的Kernel module(內核模塊) module-param-exam.c 是一個利用模塊參數和 sysfs 來進行用戶態與內核態數據交互的例子。
該模塊有三個參數可以通過命令行設置,下面是作者係統上的運行結果示例:

$ insmod ./module-param-exam.ko my_invisible_int=10 my_visible_int=20 mystring="Hello,World"my_invisible_int = 10my_visible_int = 20mystring = 'Hello,World'$ ls /sys/module/module_param_exam/parameters/mystring my_visible_int$ cat /sys/module/module_param_exam/parameters/mystringHello,World$ cat /sys/module/module_param_exam/parameters/my_visible_int20$ echo 2000 > /sys/module/module_param_exam/parameters/my_visible_int$ cat /sys/module/module_param_exam/parameters/my_visible_int2000$ echo "abc" > /sys/module/module_param_exam/parameters/mystring$ cat /sys/module/module_param_exam/parameters/mystringabc$ rmmod module_param_exammy_invisible_int = 10my_visible_int = 2000mystring = 'abc'

3、sysctl

Sysctl是一種用戶應用來設置和獲得運行時內核的配置參數的一種有效方式,通過這種方式,用戶應用可以在內核運行的任何時刻來改變內核的配置參數,也可以在任何時候獲得內核的配置參數,通常,內核的這些配置參數也出現在proc文件系統的/proc/sys目錄下,用戶應用可以直接通過這個目錄下的文件來實現內核配置的讀寫操作,例如,用戶可以通過

cat /proc/sys/net/ipv4/ip_forward

來得知內核IP層是否允許轉發IP包,用戶可以通過

echo 1 > /proc/sys/net/ipv4/ip_forward

把內核IP 層設置為允許轉發IP 包,即把該機器配置成一個路由器或網關。
一般地,所有的Linux 發布也提供了一個系統工具sysctl,它可以設置和讀取內核的配置參數,但是該工具依賴於proc 文件系統,為了使用該工具,內核必須支持proc 文件系統。

下面是使用sysctl 工具來獲取和設置內核配置參數的例子:

$ sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 0$ sysctl -w net.ipv4.ip_forward=1net.ipv4.ip_forward = 1$ sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 1

注意,參數net.ipv4.ip_forward 實際被轉換到對應的proc 文件/proc/sys/net/ipv4/ip_forward,選項-w 表示設置該內核配置參數,沒有選項表示讀內核配置參數,用戶可以使用sysctl – a 來讀取所有的內核配置參數,對應更多的sysctl 工具的信息,請參考手冊頁sysctl(8)

但是proc 文件系統對sysctl 不是必須的,在沒有proc 文件系統的情況下,仍然可以,這時需要使用內核提供的系統調用sysctl 來實現對內核配置參數的設置和讀取。
在源代碼包中給出了一個實際例子程序,它說明瞭如何在內核和用戶態使用sysctl。

頭文件 sysctl-exam.h 定義了sysctl條目ID,用戶態應用和內核模塊需要這些ID來操作和註冊sysctl條目。
內核模塊在文件 sysctl-exam-kern.c 中實現,在該內核模塊中,每一個sysctl條目對應一個struct ctl_table結構,該結構定義了要註冊的sysctl條目的ID(字段ctl_name),在proc下的名稱(字段procname),對應的內核變量(字段data,注意該該字段的賦值必須是指針),條目允許的最大長度(字段maxlen,它主要用於字符串內核變量,以便在對該條目設置時,對超過該最大長度的字符串截掉後面超長的部分),條目在proc文件系統下的訪問權限(字段mode),在通過proc設置時的處理函數(字段proc_handler,對於整型內核變量,應當設置為&proc_dointvec,而對於字符串內核變量,則設置為&proc_dostring),字符串處理策略(字段strategy,一般這是為&sysctl_string)。

Sysctl 條目可以是目錄,此時mode 字段應當設置為0555,否則通過sysctl 系統調用將無法訪問它下面的sysctl 條目,child 則指向該目錄條目下面的所有條目,對於在同一目錄下的多個條目,不必一一註冊,用戶可以把它們組織成一個struct ctl_table 類型的數組,然後一次註冊就可以,但此時必須把數組的最後一個結構設置為NULL,即

{	.ctl_name = 0}

註冊sysctl條目使用函數 register_sysctl_table(struct ctl_table *, int),第一個參數為定義的struct ctl_table結構的sysctl條目或條目數組指針,第二個參數為插入到sysctl條目表中的位置,如果插入到末尾,應當為0,如果插入到開頭,則為非0。內核把所有的sysctl條目都組織成sysctl表。

當模塊卸載時,需要使用函數unregister_sysctl_table(struct ctl_table_header *)解註冊通過函數register_sysctl_table註冊的sysctl條目,函數register_sysctl_table在調用成功時返回結構struct ctl_table_header,它就是sysctl表的表頭,解註冊函數使用它來卸載相應的sysctl條目。

用戶態應用 sysctl-exam-user.c 通過sysctl系統調用來查看和設置前面內核模塊​​註冊的sysctl條目(當然如果用戶的系統內核已經支持proc文件系統,可以直接使用文件操作應用如cat, echo等直接查看和設置這些sysctl條目)。

下面是作者運行該模塊與應用的輸出結果示例:

$ insmod ./sysctl-exam-kern.ko$ cat /proc/sys/mysysctl/myint0$ cat /proc/sys/mysysctl/mystring$ ./sysctl-exam-usermysysctl.myint = 0mysysctl.mystring = ""$ ./sysctl-exam-user 100 "Hello, World"old value: mysysctl.myint = 0new value: mysysctl.myint = 100old vale: mysysctl.mystring = ""new value: mysysctl.mystring = "Hello, World"$ cat /proc/sys/mysysctl/myint100$ cat /proc/sys/mysysctl/mystringHello, World

4、系統調用

系統調用是內核提供給應用程序的接口,應用對底層硬件的操作大部分都是通過調用系統調用來完成的,例如得到和設置系統時間,就需要分別調用 gettimeofday 和 settimeofday 來實現。
事實上,所有的系統調用都涉及到內核與應用之間的數據交換,如文件系統操作函數read 和write,設置和讀取網絡協議棧的setsockopt 和getsockopt。
本節並不是講解如何增加新的系統調用,而是講解如何利用現有系統調用來實現用戶的數據傳輸需求。

一般地,用戶可以建立一個偽設備來作為應用與內核之間進行數據交換的渠道,最通常的做法是使用偽字符設備,具體實現方法是:
1.定義對字符設備進行操作的必要函數並設置結構struct file_operations
結構 struct file_operations 非常大,對於一般的數據交換需求,只定義open, read, write, ioctl, mmap 和release 函數就足夠了,它們實際上對應於用戶態的文件系統操作函數open, read, write, ioctl , mmap 和close。

這些函數的原型示例如下:

ssiz​​e_t exam_read (struct file * file, char __user * buf, size_t count, loff_t * ppos){…}ssiz​​e_t exam_write(struct file * file, const char __user * buf, size_t count,  loff_t * ppos){…}int exam_ioctl(struct inode * inode, struct file * file, unsigned int cmd,  unsigned long argv){…}int exam_mmap(struct file *, struct vm_area_struct *){…}int exam_open(struct inode * inode, struct file * file){…}int exam_release(struct inode * inode, struct file * file){…}

在定義了這些操作函數後需要定義並設置結構 struct file_operations

struct file_operations exam_file_ops = {	.owner = THIS_MODULE,	.read = exam_read,	.write = exam_write,	.ioctl = exam_ioctl,	.mmap = exam_mmap,	.open = exam_open,	.release = exam_release,};

2. 註冊定義的偽字符設備並把它和上面的struct file_operations 關聯起來:

int exam_char_dev_major;exam_char_dev_major = register_chrdev(0, "exam_char_dev", &exam_file_ops);

注意,函數register_chrdev 的第一個參數如果為0,表示由內核來確定該註冊偽字符設備的主設備號,這是該函數的返回為實際分配的主設備號,如果返回小於0,表示註冊失敗。
因此,用戶在使用該函數時必須判斷返回值以便處理失敗情況。為了使用該函數必須包含頭文件 linux/fs.h

在源代碼包中給出了一個使用這種方式實現用戶態與內核態數據交換的典型例子,它包含了三個文件:

頭文件syscall-exam.h 定義了ioctl 命令,
.c 文件syscall-exam -user.c為用戶態應用,它通過文件系統操作函數mmap 和ioctl 來與內核態模塊交換數據,
.c 文件syscall-exam-kern.c 為內核模塊,它實現了一個偽字符設備,以便與用戶態應用進行數據交換。

為了正確運行應用程序 syscall-exam-user,需要在插入模塊 syscall-exam-kern 後創建該實現的偽字符設備,用戶可以使用下面命令來正確創建設備:

$ mknod /dev/mychrdev c `dmesg | grep "char device mychrdev" | sed 's/.*major is //g'` 0

然後用戶可以通過cat 來讀寫 /dev/mychrdev,應用程序 syscall-exam-user 則使用 mmap 來讀數據並使用ioctl 來得到該字符設備的信息以及裁減數據內容,它只是示例如何使用現有的系統調用來實現用戶需要的數據交互操作。
下面是作者運行該模塊的結果示例:

$ insmod ./syscall-exam-kern.kochar device mychrdev is registered, major is 254$ mknod /dev/mychrdev c `dmesg | grep "char device mychrdev" | sed 's/.*major is //g'` 0$ cat /dev/mychrdev$ echo "abcdefghijklmnopqrstuvwxyz" > /dev/mychrdev$ cat /dev/mychrdevabcdefghijklmnopqrstuvwxyz$ ./syscall-exam-userUser process: syscall-exam-us(1433)Available space: 65509 bytesData len: 27 bytesOffset in physical: cc0 bytesmychrdev content by mmap:abcdefghijklmnopqrstuvwxyz$ cat /dev/mychrdevabcde

5、netlink

Netlink 是一種特殊的socket,它是Linux 所特有的,類似於BSD 中的 AF_ROUTE 但又遠比它的功能強大,目前在最新的Linux 內核(2.6.14)中使用netlink 進行應用與內核通信的應用很多,包括:路由daemon(NETLINK_ROUTE),1-wire 子系統(NETLINK_W1),用戶態socket 協議(NETLINK_USERSOCK),防火牆(NETLINK_FIREWALL),socket 監視(NETLINK_INET_DIAG),netfilter 日誌(NETLINK_NFLOG),ipsec 安全策略(NETLINK_XFRM ),SELinux 事件通知(NETLINK_SELINUX),iSCSI 子系統(NETLINK_ISCSI),進程審計(NETLINK_AUDIT),轉發信息表查詢(NETLINK_FIB_LOOKUP),netlink connector(NETLINK_CONNECTOR),netfilter 子系統(NETLINK_NETFILTER),IPv6 防火牆(NETLINK_IP6_FW),DECnet路由信息(NETLINK_DNRTMSG),內核事件向用戶態通知(NETLINK_KOBJECT_UEVENT),通用netlink(NETLINK_GENERIC)。

Netlink 是一種在內核與用戶應用間進行雙向數據傳輸的非常好的方式,用戶態應用使用標準的socket API 就可以使用netlink 提供的強大功能,內核態需要使用專門的內核API 來使用netlink。
Netlink 相對於系統調用,ioctl 以及/proc 文件系統而言具有以下優點:

1,為了使用netlink,用戶僅需要在include/linux/netlink.h 中增加一個新類型的netlink 協議定義即可, 如#define NETLINK_MYTEST 17 然後,內核和用戶態應用就可以立即通過socket API 使用該netlink協議類型進行數據交換。但係統調用需要增加新的系統調用,ioctl 則需要增加設備或文件,​​ 那需要不少代碼,proc 文件系統則需要在/proc 下添加新的文件或目錄,那將使本來就混亂的/proc 更加混亂。

2. netlink是一種異步通信機制,在內核與用戶態應用之間傳遞的消息保存在socket緩存隊列中,發送消息只是把消息保存在接收者的socket的接收隊列,而不需要等待接收者收到消息,但係統調用與ioctl 則是同步通信機制,如果傳遞的數據太長,將影響調度粒度。

3.使用netlink 的內核部分可以採用模塊的方式實現,使用netlink 的應用部分和內核部分沒有編譯時依賴,但係統調用就有依賴,而且新的系統調用的實現必須靜態地連接到內核中,它無法在模塊中實現,使用新系統調用的應用在編譯時需要依賴內核。

4.netlink 支持多播,內核模塊或應用可以把消息多播給一個netlink組,屬於該neilink 組的任何內核模塊或應用都能接收到該消息,內核事件向用戶態的通知機制就使用了這一特性,任何對內核事件感興趣的應用都能收到該子系統發送的內核事件,在後面的文章中將介紹這一機制的使用。

5.內核可以使用netlink 首先發起會話,但係統調用和ioctl 只能由用戶應用發起調用。

6.netlink 使用標準的socket API,因此很容易使用,但係統調用和ioctl則需要專門的培訓才能使用。

1) 用戶態使用netlink

用戶態應用使用標準的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和close() 就能很容易地使用netlink socket,查詢手冊頁可以了解這些函數的使用細節,本文只是講解使用netlink 的用戶應該如何使用這些函數。注意,使用netlink 的應用必須包含頭文件linux/netlink.h。當然socket 需要的頭文件也必不可少,sys/socket.h。

為了創建一個netlink socket,用戶需要使用如下參數調用socket():

socket(AF_NETLINK, SOCK_RAW, netlink_type)

第一個參數必須是AF_NETLINK 或PF_NETLINK,在Linux 中,它們倆實際為一個東西,它表示要使用netlink,第二個參數必須是SOCK_RAW或SOCK_DGRAM, 第三個參數指定netlink協議類型,如前面講的用戶自定義協議類型NETLINK_MYTEST, NETLINK_GENERIC是一個通用的協議類型,它是專門為用戶使用的,因此,用戶可以直接使用它,而不必再添加新的協議類型。

內核預定義的協議類型有:

#define NETLINK_ROUTE 0 /* Routing/device hook */#define NETLINK_W1 1 /* 1-wire subsystem */#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

對於每一個netlink協議類型,可以有多達32多播組,每一個多播組用一個位表示,netlink 的多播特性使得發送消息給同一個組僅需要一次系統調用,因而對於需要多撥消息的應用而言,大大地降低了系統調用的次數。函數bind() 用於把一個打開的netlink socket 與netlink 源socket 地址綁定在一起。

netlink socket 的地址結構如下:

struct sockaddr_nl{  sa_family_t nl_family;  unsigned short nl_pad;  __u32 nl_pid;  __u32 nl_groups;};

字段nl_family 必須設置為 AF_NETLINK 或 著PF_NETLINK,字段 nl_pad 當前沒有使用,因此要總是設置為0,字段nl_pid 為接收或發送消息的進程的ID,如果希望內核處理消息或多播消息,就把該字段設置為0,否則設置為處理消息的進程ID。字段nl_groups 用於指定多播組,bind 函數用於把調用進程加入到該字段指定的多播組,如果設置為0,表示調用者不加入任何多播組。

傳遞給bind 函數的地址的nl_pid 字段應當設置為本進程的進程ID,這相當於netlink socket 的本地地址。
但是,對於一個進程的多個線程使用netlink socket 的情況,字段nl_pid 則可以設置為其它的值,如:

pthread_self() << 16 | getpid();

因此字段nl_pid 實際上未必是進程ID,它只是用於區分不同的接收者或發送者的一個標識,用戶可以根據自己需要設置該字段。函數bind 的調用方式如下:

bind(fd, (struct sockaddr*)&nladdr, sizeof(struct sockaddr_nl));

fd為前面的socket 調用返回的文件描述符,參數nladdr 為struct sockaddr_nl 類型的地址。
為了發送一個netlink 消息給內核或其他用戶態應用,需要填充目標netlink socket 地址,此時,字段nl_pid 和nl_groups 分別表示接收消息者的進程ID 與多播組。如果字段nl_pid 設置為0,表示消息接收者為內核或多播組,如果nl_groups為0,表示該消息為單播消息,否則表示多播消息。

使用函數sendmsg 發送netlink 消息時還需要引用結構struct msghdr、struct nlmsghdr 和struct iovec,結構struct msghdr 需如下設置:

struct msghdr msg;memset(&msg, 0, sizeof(msg));msg.msg_name = (void *)&(nladdr);msg.msg_namelen = sizeof(nladdr);

其中nladdr 為消息接收者的netlink 地址。
struct nlmsghdr 為netlink socket 自己的消息頭,這用於多路復用和多路分解netlink 定義的所有協議類型以及其它一些控制,netlink 的內核實現將利用這個消息頭來多路復用和多路分解已經其它的一些控制,因此它也被稱為netlink 控制塊。因此,應用在發送netlink 消息時必須提供該消息頭。

struct nlmsghdr{  __u32 nlmsg_len; /* Length of message */  __u16 nlmsg_type; /* Message type*/  __u16 nlmsg_flags; /* Additional flags */  __u32 nlmsg_seq; /* Sequence number */  __u32 nlmsg_pid; /* Sending process PID */};

字段nlmsg_len 指定消息的總長度,包括緊跟該結構的數據部分長度以及該結構的大小,字段nlmsg_type 用於應用內部定義消息的類型,它對netlink 內核實現是透明的,因此大部分情況下設置為0,字段nlmsg_flags 用於設置消息標誌,可用的標誌包括:

/* Flags values​​ */#define NLM_F_REQUEST 1 /* It is request message. */#define NLM_F_MULTI 2 /* Multipart message, terminated by NLMSG_DONE */#define NLM_F_ACK 4 /* Reply with ack, with zero or error code */#define NLM_F_ECHO 8 /* Echo this request *//* Modifiers to GET request */#define NLM_F_ROOT 0x100 /* specify tree root */#define NLM_F_MATCH 0x200 /* return all matching */#define NLM_F_ATOMIC 0x400 /* atomic GET */#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)/* Modifiers to NEW request */#define NLM_F_REPLACE 0x100 /* Override existing */#define NLM_F_EXCL 0x200 /* Do not touch, if it exists */#define NLM_F_CREATE 0x400 /* Create, if it does not exist */#define NLM_F_APPEND 0x800 /* Add to end of list */

標誌NLM_F_REQUEST用於表示消息是一個請求,所有應用首先發起的消息都應設置該標誌。
標誌NLM_F_MULTI 用於指示該消息是一個多部分消息的一部分,後續的消息可以通過宏NLMSG_NEXT來獲得。
宏NLM_F_ACK表示該消息是前一個請求消息的響應,順序號與進程ID可以把請求與響應關聯起來。
標誌NLM_F_ECHO表示該消息是相關的一個包的回傳。
標誌NLM_F_ROOT 被許多netlink 協議的各種數據獲取操作使用,該標誌指示被請求的數據表應當整體返回用戶應用,而不是一個條目一個條目地返回。有該標誌的請求通常導致響應消息設置NLM_F_MULTI標誌。注意,當設置了該標誌時,請求是協議特定的,因此,需要在字段nlmsg_type 中指定協議類型。
標誌NLM_F_MATCH 表示該協議特定的請求只需要一個數據子集,數據子集由指定的協議特定的過濾器來匹配。
標誌NLM_F_ATOMIC 指示請求返回的數據應當原子地收集,這預防數據在獲取期間被修改。
標誌NLM_F_DUMP 未實現。
標誌NLM_F_REPLACE 用於取代在數據表中的現有條目。
標誌NLM_F_EXCL_ 用於和CREATE 和APPEND 配合使用,如果條目已經存在,將失敗。
標誌NLM_F_CREATE 指示應當在指定的表中創建一個條目。
標誌NLM_F_APPEND 指示在表末尾添加新的條目。

內核需要讀取和修改這些標誌,對於一般的使用,用戶把它設置為0 就可以,只是一些高級應用(如netfilter 和路由daemon 需要它進行一些複雜的操作),字段nlmsg_seq 和nlmsg_pid 用於應用追踪消息,前者表示順序號,後者為消息來源進程ID。下面是一個示例:

#define MAX_MSGSIZE 1024char buffer[] = "An example message";struct nlmsghdr nlhdr;nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));strcpy(NLMSG_DATA(nlhdr),buffer);nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer));nlhdr->nlmsg_pid = getpid(); /* self pid */nlhdr->nlmsg_flags = 0;

結構struct iovec 用於把多個消息通過一次系統調用來發送,下面是該結構使用示例:

struct iovec iov;iov.iov_base = (void *)nlhdr;iov.iov_len = nlh->nlmsg_len;msg.msg_iov = &iov;msg.msg_iovlen = 1;

在完成以上步驟後,消息就可以通過下面語句直接發送:

sendmsg(fd, &msg, 0);

應用接收消息時需要首先分配一個足夠大的緩存來保存消息頭以及消息的數據部分,然後填充消息頭,添完後就可以直接調用函數recvmsg() 來接收。

#define MAX_NL_MSG_LEN 1024struct sockaddr_nl nladdr;struct msghdr msg;struct iovec iov;struct nlmsghdr * nlhdr;nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN);iov.iov_base = (void *)nlhdr;iov.iov_len = MAX_NL_MSG_LEN;msg.msg_name = (void *)&(nladdr);msg.msg_namelen = sizeof(nladdr);msg.msg_iov = &iov;msg.msg_iovlen = 1;recvmsg(fd, &msg, 0);

注意:fd為socket調用打開的netlink socket描述符。
在消息接收後,nlhdr指向接收到的消息的消息頭,nladdr保存了接收到的消息的目標地址,宏NLMSG_DATA(nlhdr)返回指向消息的數據部分的指針。
在linux/netlink.h中定義了一些方便對消息進行處理的宏,這些宏包括:

#define NLMSG_ALIGNTO 4#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

宏NLMSG_ALIGN(len)用於得到不小於len且字節對齊的最小數值。

#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))

宏NLMSG_LENGTH(len)用於計算數據部分長度為len時實際的消息長度。它一般用於分配消息緩存。

#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))

宏NLMSG_SPACE(len)返回不小於NLMSG_LENGTH(len)且字節對齊的最小數值,它也用於分配消息緩存。

#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

宏NLMSG_DATA(nlh)用於取得消息的數據部分的首地址,設置和讀取消息數據部分時需要使用該宏。

#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \                      (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))

宏NLMSG_NEXT(nlh,len)用於得到下一個消息的首地址,同時len也減少為剩餘消息的總長度,該宏一般在一個消息被分成幾個部分發送或接收時使用。

#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \                           (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \                           (nlh)->nlmsg_len <= (len))

宏NLMSG_OK(nlh,len)用於判斷消息是否有len這麼長。

#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

宏NLMSG_PAYLOAD(nlh,len)用於返回payload的長度。
函數close用於關閉打開的netlink socket。

2) netlink內核API

netlink的內核實現在.c文件net/core/af_netlink.c 中,內核模塊要想使用netlink,也必須包含頭文件linux/netlink.h。
內核使用netlink需要專門的API,這完全不同於用戶態應用對netlink的使用。如果用戶需要增加新的netlink協議類型,必須通過修改linux/netlink.h來實現,當然,目前的netlink實現已經包含了一個通用的協議類型NETLINK_GENERIC以方便用戶使用,用戶可以直接使用它而不必增加新的協議類型。

前面講到,為了增加新的netlink協議類型,用戶僅需增加如下定義到 linux/netlink.h 就可以:

#define NETLINK_MYTEST 17

只要增加這個定義之後,用戶就可以在內核的任何地方引用該協議。
在內核中,為了創建一個netlink socket用戶需要調用如下函數:

struct sock *netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len​​));

參數unit表示netlink協議類型,如NETLINK_MYTEST,參數input則為內核模塊定義的netlink消息處理函數,當有消息到達這個netlink socket時,該input函數指針就會被引用。函數指針input的參數sk實際上就是函數netlink_kernel_create返回的struct sock指針,sock實際是socket的一個內核表示數據結構,用戶態應用創建的socket在內核中也會有一個struct sock結構來表示。下面是一個input函數的示例:

void input (struct sock *sk, int len​​){ struct sk_buff *skb; struct nlmsghdr *nlh = NULL; u8 *data = NULL; while ((skb = skb_dequeue(&sk->receive_queue))        != NULL) { /* process netlink message pointed by skb->data */ nlh = (struct nlmsghdr *)skb->data; data = NLMSG_DATA(nlh); /* process netlink message with header pointed by   * nlh and data pointed by data  */ }   }

函數input()會在發送進程執行sendmsg()時被調用,這樣處理消息比較及時,但是,如果消息特別長時,這樣處理將增加系統調用sendmsg()的執行時間,對於這種情況,可以定義一個內核線程專門負責消息接收,而函數input的工作只是喚醒該內核線程,這樣sendmsg將很快返回。
函數skb = skb_dequeue(&sk->receive_queue)用於取得socket sk的接收隊列上的消息,返回為一個struct sk_buff的結構,skb->data指向實際的netlink消息。

函數skb_recv_datagram(nl_sk)也用於在netlink socket nl_sk上接收消息,與skb_dequeue的不同指出是,如果socket的接收隊列上沒有消息,它將導致調用進程睡眠在等待隊列nl_sk->sk_sleep,因此它必須在進程上下文使用,剛才講的內核線程就可以採用這種方式來接收消息。

下面的函數input就是這種使用的示例:

void input (struct sock *sk, int len​​){  wake_up_interruptible(sk->sk_sleep);}

當內核中發送netlink消息時,也需要設置目​​標地址與源地址,而且內核中消息是通過struct sk_buff來管理的, linux/netlink.h中定義了一個宏:

#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。

在內核中,模塊調用函數netlink_unicast 來發送單播消息:

int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);

參數sk為函數netlink_kernel_create()返回的socket,參數skb存放消息,它的data字段指向要發送的netlink消息結構,而skb的控制塊保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用於方便設置該控制塊, 參數pid為接收消息進程的pid,參數nonblock表示該函數是否為非阻塞,如果為1,該函數將在沒有接收緩存可利用時立即返回,而如果為0,該函數在沒有接收緩存可利用時睡眠。

內核模塊或子系統也可以使用函數netlink_broadcast來發送廣播消息:

void netlink_broadcast(struct sock *sk, struct sk_buff *skb,       u32 pid, u32 group, int allocation);

前面的三個參數與netlink_unicast相同,參數group為接收消息的多播組,該參數的每一個代表一個多播組,因此如果發送給多個多播組,就把該參數設置為多個多播組組ID的位或。參數allocation為內核內存分配類型,一般地為GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用於原子的上下文(即不可以睡眠),而GFP_KERNEL用於非原子上下文。

在內核中使用函數sock_release來釋放函數netlink_kernel_create()創建的netlink socket:

void sock_release(struct socket * sock);

注意函數netlink_kernel_create()返回的類型為struct sock,因此函數sock_release應該這種調用:

sock_release(sk->sk_socket);

sk為函數netlink_kernel_create()的返回值。

在源代碼包中給出了一個使用netlink的示例,它包括一個內核模塊 netlink-exam-kern.c 和兩個應用程序 netlink-exam-user-recv.c, netlink-exam-user-send.c 。
內核模塊必須先插入到內核,然後在一個終端上運行用戶態接收程序,在另一個終端上運行用戶態發送程序,發送程序讀取參數指定的文本文件並把它作為netlink消息的內容髮送給內核模塊,內核模塊接受該消息保存到內核緩存中,它也通過proc接口出口到procfs,因此用戶也能夠通過/proc/netlink_exam_buffer看到全部的內容,同時內核也把該消息發送給用戶態接收程序,用戶態接收程序將把接收到的內容輸出到屏幕上。

第1部分小結

本文是系列文章的第一部分,它詳細介紹了五種用戶空間與內核空間的數據交換方式,並通過實際例子程序向讀者講解瞭如何在內核開發中使用這些技術,其中內核啟動參數方式是單向的,即只能向內核傳遞,而不能從內核獲取,其餘的均可以進行雙向數據交換,即既可以從用戶應用傳遞給內核,有可以從內核傳遞給應用態應用。netlink 是一種雙向的數據交換方式,它使用起來非常簡單高效,特別是它的廣播特性在一些應用中非常方便。作者認為,它是所有這些用戶態與內核態數據交換方式中最有效的最強大的方式。
該系列文章的第二部分將詳細地講解另外三種用戶態與內核態的數據交換方式,包括procfs、seq_file、debugfs 和relayfs,有興趣的讀者請參看該系列文章第二部分。


6、procfs

procfs 是比較老的一種用戶態與內核態的數據交換方式,內核的很多數據都是通過這種方式出口給用戶的,內核的很多參數也是通過這種方式來讓用戶方便設置的。
除了sysctl出口到/proc下的參數,procfs提供的大部分內核參數是只讀的。
實際上,很多應用嚴重地依賴於procfs,因此它幾乎是必不可少的組件。
前面部分的幾個例子實際上已經使用它來出口內核數據,但是並沒有講解如何使用,本節將講解如何使用procfs。

Procfs提供瞭如下API:

struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,                                          struct proc_dir_entry *parent)

該函數用於創建一個正常的proc條目,參數name給出要建立的proc條目的名稱,參數mode給出了建立的該proc條目的訪問權限,參數parent指定建立的proc條目所在的目錄。
如果要在/proc下建立proc條目,parent應當為NULL。否則它應當為proc_mkdir返回的struct proc_dir_entry結構的指針。

extern void remove_proc_entry(const char *name, struct proc_dir_entry *parent)

該函數用於刪除上面函數創建的proc條目,參數name給出要刪除的proc條目的名稱,參數parent指定建立的proc條目所在的目錄。
-

struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir_entry *parent)

該函數用於創建一個proc目錄,參數name指定要創建的proc目錄的名稱,參數parent為該proc目錄所在的目錄。
-

extern struct proc_dir_entry *proc_mkdir_mode(const char *name, mode_t mode,                        struct proc_dir_entry *parent);struct proc_dir_entry *proc_symlink(const char * name,                struct proc_dir_entry * parent, const char * dest)

該函數用於建立一個proc條目的符號鏈接,參數name給出要建立的符號鏈接proc條目的名稱,參數parent指定符號連接所在的目錄,參數dest指定鏈接到的proc條目名稱。

struct proc_dir_entry *create_proc_read_entry(const char *name,        mode_t mode, struct proc_dir_entry *base,        read_proc_t *read_proc, void * data)

該函數用於建立一個規則的只讀proc條目,參數name給出要建立的proc條目的名稱,參數mode給出了建立的該proc條目的訪問權限,參數base指定建立的proc條目所在的目錄,參數read_proc給出讀去該proc條目的操作函數,參數data為該proc條目的專用數據,它將保存在該proc條目對應的struct file結構的private_data字段中。

struct proc_dir_entry *create_proc_info_entry(const char *name,        mode_t mode, struct proc_dir_entry *base, get_info_t *get_info)

該函數用於創建一個info型的proc條目,參數name給出要建立的proc條目的名稱,參數mode給出了建立的該proc條目的訪問權限,參數base指定建立的proc條目所在的目錄,參數get_info指定該proc條目的get_info操作函數。實際上get_info等同於read_proc,如果proc條目沒有定義個read_proc,對該proc條目的read操作將使用get_info取代,因此它在功能上非常類似於函數create_proc_read_entry。

struct proc_dir_entry *proc_net_create(const char *name,        mode_t mode, get_info_t *get_info)

該函數用於在/proc/net目錄下創建一個proc條目,參數name給出要建立的proc條目的名稱,參數mode給出了建立的該proc條目的訪問權限,參數get_info指定該proc條目的get_info操作函數。

struct proc_dir_entry *proc_net_fops_create(const char *name,        mode_t mode, struct file_operations *fops)

該函數也用於在/proc/net下創建proc條目,但是它也同時指定了對該proc條目的文件操作函數。

void proc_net_remove(const char *name)

該函數用於刪除前面兩個函數在/proc/net目錄下創建的proc條目。參數name指定要刪除的proc名稱。

除了這些函數,值得一提的是結構struct proc_dir_entry,為了創建一了可寫的proc條目並指定該proc條目的寫操作函數,必須設置上面的這些創建proc條目的函數返回的指針指向的struct proc_dir_entry結構的write_proc字段,並指定該proc條目的訪問權限有寫權限。
為了使用這些接口函數以及結構struct proc_dir_entry,用戶必須在模塊中包含頭文件linux/proc_fs.h。
在源代碼包中給出了procfs示例程序procfs_exam.c,它定義了三個proc文件條目和一個proc目錄條目,讀者在插入該模塊後應當看到如下結構:

$ ls /proc/myproctestaint astring bigprocfile

讀者可以通過cat和echo等文件操作函數來查看和設置這些proc文件。特別需要指出,bigprocfile是一個大文件(超過一個​​內存頁),對於這種大文件,procfs有一些限制,因為它提供的緩存,只有一個頁,因此必須特別小心,並對超過頁的部分做特別的考慮,處理起來比較複雜並且很容易出錯,所有procfs並不適合於大數據量的輸入輸出,後面一節seq_file就是因為這一缺陷而設計的,當然seq_file依賴於procfs的一些基礎功能。

7、seq_file

一般地,內核通過在 procfs 文件系統下建立文件來向用戶空間提供輸出信息,用戶空間可以通過任何文本閱讀應用查看該文件信息,但是procfs有一個缺陷,如果輸出內容大於1個內存頁,需要多次讀,因此處理起來很難,另外,如果輸出太大,速度比較慢,有時會出現一些意想不到的情況,Alexander Viro實現了一套新的功能,使得內核輸出大文件信息更容易,該功能出現在2.4.15(包括2.4.15)以後的所有2.4內核以及2.6內核中,尤其是在2.6內核中,已經大量地使用了該功能。

要想使用seq_file功能,開發者需要包含頭文件 linux/seq_file.h,並定義與設置一個seq_operations結構(類似於file_operations結構):

struct seq_operations {        void * (*start) (struct seq_file *m, loff_t *pos);        void (*stop) (struct seq_file *m, void *v);        void * (*next) (struct seq_file *m, void *v, loff_t *pos);        int (*show) (struct seq_file *m, void *v);};

start函數用於指定seq_file文件的讀開始位置,返回實際讀開始位置,如果指定的位置超過文件末尾,應當返回NULL,start函數可以有一個特殊的返回SEQ_START_TOKEN,它用於讓show函數輸出文件頭,但這只能在pos為0時使用,next函數用於把seq_file文件的當前讀位置移動到下一個讀位置,返回實際的下一個讀位置,如果已經到達文件末尾,返回NULL,stop函數用於在讀完seq_file文件後調用,它類似於文件操作close,用於做一些必要的清理,如釋放內存等,show函數用於格式化輸出,如果成功返回0,否則返回出錯碼。

Seq_file也定義了一些輔助函數用於格式化輸出:

int seq_putc(struct seq_file *m, char c);

函數seq_putc用於把一個字符輸出到seq_file文件。

int seq_puts(struct seq_file *m, const char *s);

函數seq_puts則用於把一個字符串輸出到seq_file文件。

int seq_escape(struct seq_file *, const char *, const char *);

函數seq_escape類似於seq_puts,只是,它將把第一個字符串參數中出現的包含在第二個字符串參數中的字符按照八進制形式輸出,也即對這些字符進行轉義處理。

int seq_printf(struct seq_file *, const char *, ...)        __attribute__ ((format (printf,2,3)));

函數seq_printf是最常用的輸出函數,它用於把給定參數按照給定的格式輸出到seq_file文件。

int seq_path(struct seq_file *, struct vfsmount *, struct dentry *, char *);

函數seq_path則用於輸出文件名,字符串參數提供需要轉義的文件名字符,它主要供文件系統使用。

在定義了結構struct seq_operations之後,用戶還需要把打開seq_file文件的open函數,以便該結構與對應於seq_file文件的struct file結構關聯起來,例如,struct seq_operations定義為:

struct seq_operations exam_seq_ops = {	.start = exam_seq_start,   .stop = exam_seq_stop,   .next = exam_seq_next,   .show = exam_seq_show};

那麼,open函數應該如下定義:

static int exam_seq_open(struct inode *inode, struct file *file){        return seq_open(file, &exam_seq_ops);};

注意,函數seq_open是seq_file提供的函數,它用於把struct seq_operations結構與seq_file文件關聯起來。

最後,用戶需要如下設置struct file_operations結構:

struct file_operations exam_seq_file_ops = {        .owner = THIS_MODULE,        .open = exm_seq_open,        .read = seq_read,        .llseek = seq_lseek,        .release = seq_release};

注意,用戶僅需要設置open函數,其它的都是seq_file提供的函數。
然後,用戶創建一個/proc文件並把它的文件操作設置為exam_seq_file_ops即可:

struct proc_dir_entry *entry;entry = create_proc_entry("exam_seq_file", 0, NULL);if (entry)entry->proc_fops = &exam_seq_file_ops;

對於簡單的輸出,seq_file用戶並不需要定義和設置這麼多函數與結構,它僅需定義一個show函數,然後使用single_open來定義open函數就可以,以下是使用這種簡單形式的一般步驟:
1.定義一個show函數

int exam_show(struct seq_file *p, void *v){…}

2. 定義open函數

int exam_single_open(struct inode *inode, struct file *file){        return(single_open(file, exam_show, NULL));}

注意要使用single_open而不是seq_open。

3. 定義struct file_operations結構

struct file_operations exam_single_seq_file_operations = {        .open = exam_single_open,        .read = seq_read,        .llseek = seq_lseek,        .release = single_release,};

注意,如果open函數使用了single_open,release函數必須為single_release,而不是seq_release。
在源代碼包中給出了一個使用seq_file的具體例子seqfile_exam.c,它使用seq_file提供了一個查看當前系統運行的所有進程的/proc接口,在編譯並插入該模塊後,用戶通過命令"cat / proc/ exam_esq_file"可以查看系統的所有進程。

8、debugfs

內核開發者經常需要向用戶空間應用輸出一些調試信息,在穩定的系統中可能根本不需要這些調試信息,但是在開發過程中,為了搞清楚內核的行為,調試信息非常必要,printk可能是用的最多的,但它並不是最好的,調試信息只是在開發中用於調試,而printk將一直輸出,因此開發完畢後需要清除不必要的printk語句,另外如果開發者希望用戶空間應用能夠改變內核行為時,printk就無法實現。因此,需要一種新的機制,那隻有在需要的時候使用,它在需要時通過在一個虛擬文件系統中創建一個或多個文件來向用戶空間應用提供調試信息。

有幾種方式可以實現上述要求:

-使用procfs,在/proc創建文件輸出調試信息,但是procfs對於大於一個內存頁(對於x86是4K)的輸出比較麻煩,而且速度慢,有時回出現一些意想不到的問題。
-使用sysfs(2.6內核引入的新的虛擬文件系統),在很多情況下,調試信息可以存放在那裡,但是sysfs主要用於系統管理,它希望每一個文件對應內核的一個變量,如果使用它輸出複雜的數據結構或調試信息是非常困難的。
-使用libfs創建一個新的文件系統,該方法極其靈活,開發者可以為新文件系統設置一些規則,使用libfs使得創建新文件系統更加簡單,但是仍然超出了一個開發者的想像。

為了使得開發者更加容易使用這樣的機制,Greg Kroah-Hartman開發了debugfs(在2.6.11中第一次引入),它是一個虛擬文件系統,專門用於輸出調試信息,該文件系統非常小,很容易使用,可以在配置內核時選擇是否構件到內核中,在不選擇它的情況下,使用它提供的API的內核部分不需要做任何改動。

使用debugfs的開發者首先需要在文件系統中創建一個目錄,下面函數用於在debugfs文件系統下創建一個目錄:

        struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);

參數name是要創建的目錄名,參數parent指定創建目錄的父目錄的dentry,如果為NULL,目錄將創建在debugfs文件系統的根目錄下。如果返回為-ENODEV,表示內核沒有把debugfs編譯到其中,如果返回為NULL,表示其他類型的創建失敗,如果創建目錄成功,返回指向該目錄對應的dentry條目的指針。

下面函數用於在debugfs文件系統中創建一個文件:

        struct dentry *debugfs_create_file(const char *name, mode_t mode,                                       struct dentry *parent, void *data,                                       struct file_operations *fops);

參數name指定要創建的文件名,參數mode指定該文件的訪問許可,參數parent指向該文件所在目錄,參數data為該文件特定的一些數據,參數fops為實現在該文件上進行文件操作的fiel_operations結構指針,在很多情況下,由seq_file(前面章節已經講過)提供的文件操作實現就足夠了,因此使用debugfs很容易,當然,在一些情況下,開發者可能僅需要使用用戶應用可以控制的變量來調試,debugfs也提供了4個這樣的API方便開發者使用:

    struct dentry *debugfs_create_u8(const char *name, mode_t mode,                                      struct dentry *parent, u8 *value);    struct dentry *debugfs_create_u16(const char *name, mode_t mode,                                       struct dentry *parent, u16 *value);    struct dentry *debugfs_create_u32(const char *name, mode_t mode,                                       struct dentry *parent, u32 *value);    struct dentry *debugfs_create_bool(const char *name, mode_t mode,                                       struct dentry *parent, u32 *value);

參數name和mode指定文件名和訪問許可,參數value為需要讓用戶應用控制的內核變量指針。
當內核模塊卸載時,Debugfs並不會自動清除該模塊創建的目錄或文件,因此對於創建的每一個文件或目錄,開發者必須調用下面函數清除:

        void debugfs_remove(struct dentry *dentry);

參數dentry為上面創建文件和目錄的函數返回的dentry指針。

在源代碼包中給出了一個使用debufs的示例模塊debugfs_exam.c,為了保證該模塊正確運行,必須讓內核支持debugfs,debugfs是一個調試功能,因此它位於主菜單Kernel hacking,並且必須選擇Kernel debugging選項才能選擇,它的選項名稱為Debug Filesystem。

為了在用戶態使用debugfs,用戶必須mount它,下面是在作者係統上的使用輸出:

$ mkdir -p /debugfs
$ mount -t debugfs debugfs /debugfs
$ insmod ./debugfs_exam.ko
$ ls /debugfs
debugfs-exam

$ ls /debugfs/debugfs-exam
u8_var u16_var u32_var bool_var
$ cd /debugfs/debugfs-exam
$ cat u8_var
0

$ echo 200 > u8_var
$ cat u8_var
200

$ cat bool_var
N

$ echo 1 > bool_var
$ cat bool_var
Y

9、relayfs

relayfs是一個快速的轉發(relay)數據的文件系統,它以其功能而得名。
它為那些需要從內核空間轉發大量數據到用戶空間的工具和應用提供了快速有效的轉發機制。

Channel是relayfs文件系統定義的一個主要概念,每一個channel由一組內核緩存組成,每一個CPU有一個對應於該channel的內核緩存,每一個內核緩存用一個在relayfs文件系統中的文件文件表示,內核使用relayfs提供的寫函數把需要轉發給用戶空間的數據快速地寫入當前CPU上的channel內核緩存,用戶空間應用通過標準的文件I/O函數在對應的channel文件中可以快速地取得這些被轉發出的數據mmap來。寫入到channel中的數據的格式完全取決於內核中創建channel的模塊或子系統。

relayfs的用戶空間API:
relayfs實現了四個標準的文件I/O函數,open、mmap、poll和close

open(),打開一個channel在某一個CPU上的緩存對應的文件。
mmap(),把打開的channel緩存映射到調用者進程的內存空間。
read(),讀取channel緩存,隨後的讀操作將看不到被該函數消耗的字節,如果channel的操作模式為非覆蓋寫,那麼用戶空間應用在有內核模塊寫時仍可以讀取,但是如果channel的操作模式為覆蓋式,那麼在讀操作期間如果有內核模塊進行寫,結果將無法預知,​​因此對於覆蓋式寫的channel,用戶應當在確認在channel的寫完全結束後再進行讀。
poll(),用於通知用戶空間應用轉發數據跨越了子緩存的邊界,支持的輪詢標誌有POLLIN、POLLRDNORM和POLLERR。
close(),關閉open函數返回的文件描述符,如果沒有進程或內核模塊打開該channel緩存,close函數將釋放該channel緩存。

注意:用戶態應用在使用上述API時必須保證已經掛載了relayfs文件系統,但內核在創建和使用channel時不需要relayfs已經掛載。下面命令將把relayfs文件系統掛載到/mnt/relay。

		mount -t relayfs relayfs /mnt/relay

relayfs內核API:
relayfs提供給內核的API包括四類:channel管理、寫函數、回調函數和輔助函數。

relay_open(base_filename, parent, subbuf_size, n_subbufs, overwrite, callbacks)
Channel管理函數包括:
relay_close(chan)
relay_flush(chan)
relay_reset(chan)
relayfs_create_dir(name, parent)
relayfs_remove_dir(dentry)
relay_commit(buf, reserved, count)
relay_subbufs_consumed(chan, cpu, subbufs_consumed)

寫函數包括:
relay_write(chan, data, length)
__relay_write(chan, data, length)
relay_reserve(chan, length)

回調函數包括:
subbuf_start(buf, subbuf, prev_subbuf_idx, prev_subbuf)
buf_mapped(buf, filp)
buf_unmapped(buf, filp)

輔助函數包括:
relay_buf_full(buf)
subbuf_start_reserve(buf, length)

前面已經講過,每一個channel由一組channel緩存組成,每個CPU對應一個該channel的緩存,每一個緩存又由一個或多個子緩存組成,每一個緩存是子緩存組成的一個環型緩存。
函數relay_open用於創建一個channel並分配對應於每一個CPU的緩存,用戶空間應用通過在relayfs文件系統中對應的文件可以訪問channel緩存,參數base_filename用於指定channel的文件名,relay_open函數將在relayfs文件系統中創建base_filename0..base_filenameN-1,即每一個CPU對應一個channel文件,其中N為CPU數,缺省情況下,這些文件將建立在relayfs文件系統的根目錄下,但如果參數parent非空,該函數將把channel文件創建於parent目錄下,parent目錄使用函數relay_create_dir創建,函數relay_remove_dir用於刪除由函數relay_create_dir創建的目錄,誰創建的目錄,誰就負責在不用時負責刪除。

參數subbuf_size用於指定channel緩存中每一個子緩存的大小,參數n_subbufs用於指定channel緩存包含的子緩存數,因此實際的channel緩存大小為(subbuf_size x n_subbufs),參數overwrite用於指定該channel的操作模式,relayfs提供了兩種寫模式,一種是覆蓋式寫,另一種是非覆蓋式寫。

使用哪一種模式完全取決於函數subbuf_start的實現,覆蓋寫將在緩存已滿的情況下無條件地繼續從緩存的開始寫數據,而不管這些數據是否已經被用戶應用讀取,因此寫操作決不失敗。在非覆蓋寫模式下,如果緩存滿了,寫將失敗,但內核將在用戶空間應用讀取緩存數據時通過函數relay_subbufs_consumed()通知relayfs。如果用戶空間應用沒來得及消耗緩存中的數據或緩存已滿,兩種模式都將導致數據丟失,唯一的區別是,前者丟失數據在緩存開頭,而後者丟失數據在緩存末尾。

一旦內核再次調用函數relay_subbufs_consumed(),已滿的緩存將不再滿,因而可以繼續寫該緩存。當緩存滿了以後,relayfs將調用回調函數buf_full()來通知內核模塊或子系統。當新的數據太大無法寫入當前子緩存剩餘的空間時,relayfs將調用回調函數subbuf_start()來通知內核模塊或子系統將需要使用新的子緩存。

內核模塊需要在該回調函數中實現下述功能:
初始化新的子緩存;
如果1正確,完成當前子緩存;
如果2正確,返回是否正確完成子緩存切換;
在非覆蓋寫模式下,回調函數subbuf_start()應該如下實現:

static int subbuf_start(struct rchan_buf *buf,                        void *subbuf,			void *prev_subbuf,			unsigned int prev_padding){	if (prev_subbuf)		*((unsigned *)prev_subbuf) = prev_padding;	if (relay_buf_full(buf))		return 0;	subbuf_start_reserve(buf, sizeof(unsigned int));	return 1;}

如果當前緩存滿,即所有的子緩存都沒讀取,該函數返回0,指示子緩存切換沒有成功。當子緩存通過函數relay_subbufs_consumed()被讀取後,讀取者將負責通知relayfs,函數relay_buf_full()在已經有讀者讀取子緩存數據後返回0,在這種情況下,子緩存切換成功進行。

在覆蓋寫模式下, subbuf_start()的實現與非覆蓋模式類似:

static int subbuf_start(struct rchan_buf *buf,                        void *subbuf,			void *prev_subbuf,			unsigned int prev_padding){	if (prev_subbuf)		*((unsigned *)prev_subbuf) = prev_padding;	subbuf_start_reserve(buf, sizeof(unsigned int));	return 1;}

只是不做relay_buf_full()檢查,因為此模式下,緩存是環行的,可以無條件地寫。
因此在此模式下,子緩存切換必定成功,函數relay_subbufs_consumed() 也無須調用。
如果channel寫者沒有定義subbuf_start(),缺省的實現將被使用。可以通過在回調函數subbuf_start()中調用輔助函數subbuf_start_reserve()在子緩存中預留頭空間,預留空間可以保存任何需要的信息,如上面例子中,預留空間用於保存子緩存填充字節數,在subbuf_start()實現中,前一個子緩存的填充值被設置。

前一個子緩存的填充值和指向前一個子緩存的指針一道作為subbuf_start()的參數傳遞給subbuf_start(),只有在子緩存完成後,才能知道填充值。
subbuf_start()也被在channel創建時分配每一個channel緩存的第一個子緩存時調用,以便預留頭空間,但在這種情況下,前一個子緩存指針為NULL。
內核模塊使用函數relay_write()或__relay_write()往channel緩存中寫需要轉發的數據,它們的區別是前者失效了本地中斷,而後者只搶占失效,因此前者可以在任何內核上下文安全使用,而後者應當在沒有任何中斷上下文將寫channel緩存的情況下使用。這兩個函數沒有返回值,因此用戶不能直接確定寫操作是否失敗,在緩存滿且寫模式為非覆蓋模式時,relayfs將通過回調函數buf_full來通知內核模塊。

函數relay_reserve()用於在channel緩存中預留一段空間以便以後寫入,在那些沒有臨時緩存而直接寫入channel緩存的內核模塊可能需要該函數,使用該函數的內核模塊在實際寫這段預留的空間時可以通過調用relay_commit()來通知relayfs。當所有預留的空間全部寫完並通過relay_commit通知relayfs後,relayfs將調用回調函數deliver()通知內核模塊一個完整的子緩存已經填滿。由於預留空間的操作並不在寫channel的內核模塊完全控制之下,因此relay_reserve()不能很好地保護緩存,因此當內核模塊調用relay_reserve()時必須採​​取恰當的同步機制。當內核模塊結束對channel的使用後需要調用relay_close() 來關閉channel,如果沒有任何用戶在引用該channel,它將和對應的緩存全部被釋放。

函數relay_flush()強制在所有的channel緩存上做一個子緩存切換,它在channel被關閉前使用來終止和處理最後的子緩存。
函數relay_reset()用於將一個channel恢復到初始狀態,因而不必釋放現存的內存映射並重新分配新的channel緩存就可以使用channel,但是該調用只有在該channel沒有任何用戶在寫的情況下才可以安全使用。
回調函數buf_mapped() 在channel緩存被映射到用戶空間時被調用。
回調函數buf_unmapped()在釋放該映射時被調用。內核模塊可以通過它們觸發一些內核操作,如開始或結束channel寫操作。

在源代碼包中給出了一個使用relayfs的示例程序relayfs_exam.c,它只包含一個內核模塊,對於復雜的使用,需要應用程序配合。該模塊實現了類似於文章中seq_file示例實現的功能。
當然為了使用relayfs,用戶必須讓內核支持relayfs,並且要mount它,下面是作者係統上的使用該模塊的輸出信息:

$ mkdir -p /relayfs
$ insmod ./relayfs-exam.ko
$ mount -t relayfs relayfs /relayfs
$ cat /relayfs/example0

$

relayfs是一種比較複雜的內核態與用戶態的數據交換方式,本例子程序只提供了一個較簡單的使用方式,對於復雜的使用,請參考relayfs用例頁面

第2部分小結

本文是該系列文章最後一篇,它詳細地講解了其餘四種用戶空間與內核空間的數據交換方式,並通過實際例子程序向讀者講解瞭如何在內核開發中使用這些技術,其中seq_file是單向的,即只能向內核傳遞,而不能從內核獲取,而另外三種方式均可以進行雙向數據交換,即既可以從用戶應用傳遞給內核,又可以從內核傳遞給應用態應用。procfs一般用於向用戶出口少量的數據信息,或用戶通過它設置內核變量從而控制內核行為。seq_file實際上依賴於procfs,因此為了使用seq_file,必須使內核支持procfs。debugfs用於內核開發者調試使用,它比其他集中方式都方便,但是僅用於簡單類型的變量處理。relayfs是一種非常複雜的數據交換方式,要想準確使用並不容易,但是如果使用得當,它遠比procfs和seq_file功能強大。

2014/12/09

wpa_supplicant 是什麼 ? 負責wifi在user space的管理daemon

根據wiki說明, wpa_supplicant 是一個免費軟體實現了 IEEE 802.11i 管理控制 (在多平台上 Linux, FreeBSD, NetBSD, AROS, Microsoft Windows, Solaris, OS/2 ), 而且wpa_supplicant 既可達到全功能的WPA2 管理控制,它也同時實現了WPA和較舊的無線局域網安全協議。

簡介

IEEE802.11i協議(無線安全標準)作為IEEE802.11協議的一部分,制定了無線安全接入的標準。 WPA和WPA2(RSN)是無線安全標準中的兩種密鑰管理規範。 WPA(或WPA2)無線安全接入又包括使用802.1x協議認證的企業版和使用PSK(預共享密鑰)認證的個人版。 Supplicant是無線客戶端上實現WPA/802.1x認證功能的組件。 wpa_supplicant是無線客戶端上實現密鑰管理和認證的supplicant軟件(對應服務器端的軟件則為hostapd)。

wpa_supplicant功能支持

支援 WPA/IEEE 802.11i features 與 EAP methods (IEEE 802.1X Supplicant):

Supported WPA/IEEE 802.11i features
– WPA-PSK (“WPA-Personal”)
– WPA with EAP (e.g., with RADIUS authentication server) (“WPA-Enterprise”)
– key management for CCMP, TKIP, WEP104, WEP40
– WPA and full IEEE 802.11i/RSN/WPA2
– RSN: PMKSA caching, pre-authentication
– IEEE 802.11r
– IEEE 802.11w
– Wi-Fi Protected Setup (WPS)

Supported EAP methods (IEEE 802.1X Supplicant)
– EAP-TLS
– EAP-PEAP/MSCHAPv2 (both PEAPv0 and PEAPv1)
– EAP-PEAP/TLS (both PEAPv0 and PEAPv1)
– EAP-PEAP/GTC (both PEAPv0 and PEAPv1)
– EAP-PEAP/OTP (both PEAPv0 and PEAPv1)
– EAP-PEAP/MD5-Challenge (both PEAPv0 and PEAPv1)
– EAP-TTLS/EAP-MD5-Challenge
– EAP-TTLS/EAP-GTC
– EAP-TTLS/EAP-OTP
– EAP-TTLS/EAP-MSCHAPv2
– EAP-TTLS/EAP-TLS
– EAP-TTLS/MSCHAPv2
– EAP-TTLS/MSCHAP
– EAP-TTLS/PAP
– EAP-TTLS/CHAP
– EAP-SIM
– EAP-AKA
– EAP-AKA’
– EAP-PSK
– EAP-FAST
– EAP-PAX
– EAP-SAKE
– EAP-IKEv2
– EAP-GPSK
– LEAP (note: requires special support from the driver)

補充:無線網路使用者身份認證及加密
無線網路以空氣作為傳輸的介質,透過無線網路傳送資料時,任何人只要透過適當的設備,就可以收集並且竊取未經保護的資料。因此,如果以未加密的方式透過無線傳送是相當危險的!假定有意的攻擊者已準備竊聽您所傳送的資料,那麼,前述適當的設備有可能只需要一支高增益天線以及一台筆記型電腦,攻擊者就可以躲在遠處準備搜集您的資料,而整個過程中使用者可能都毫無警覺。

[WEP]
當傳送資料無可避免地會被有心人士竊取,又必須保護其安全性,最有效的方法就是對傳送的資料進行加密。起初,WEP(Wired Equivalent Privacy, 有線等效加密)為1999年9月通過的IEEE 802.11標準的一部份,當初被視為無線安全解決方案。WEP使用RC4(Rivest Cipher)加密技術達到機密性,RC4密碼鎖是屬於一種對稱串流密碼鎖。傳送端使用金鑰串流與訊息結合產生密文傳送;接收端收到密文後,再使用同一把金鑰串流處理密文還原原始資料。由於RC4演算法設計所造成的先天性弱點,一旦重覆使用金鑰串流、串流密碼鎖,且WEP採用IV的方式,讓攻擊者在搜集到夠多的資訊後,就能夠針對其重覆部份進行分析。可供運用的IV值並不大(小於一千七百萬),在繁忙的網路環境中必然會產生重覆的狀況。不過由於WEP設計相當容易實作,設備並不需要相當強大的計算能力,在許多的設備上也必定會支援。

[802.1X]
使用WEP僅是針對具有加密金鑰的機器進行加密動作,而802.1X則是對於使用者身份進行確認,而不會造成使用同一機器但不同使用者權限授權的困擾。802.1X只是一個架構,是IEEE採用IETF的可延伸身份認證協定(Extensible Authentication Protocol, EAP)制訂而成的,屬於一種架構協定。EAP是一個基礎的封裝方式,可以適於任何的鏈路層如PPP、802.3、802.11,以及各種身份認證的方式如TLS、AKA/SIM、Token card。

EAP身份驗證方式
EAP的使用者身份驗證作業是一個稱為EAP method的附屬協定所處理,這樣的優點是EAP並不需要處理使用者驗證的細節,當有不同的需求產生時,就僅需開發新的EAP method來滿足不同的需求,如MD5 Challenge、GTC、EAP-TLS、TTLS、PEAP、EAP-SIM及MS-CHAP-V2都是常見的EAP method。

[LEAP]
LEAP(Lightweight EAP)為Cisco所專屬的,為最早普遍使用的無線網路身份認證方式。相對於WEP用手動方式設定金鑰,LEAP進行二次MS-CHAP-V1交換程序,在交換MS-CHAP程序時則衍生出動態金鑰。但也因MS-CHAP-V1所致,也導致與EAP-MD5一樣易遭受字典攻擊。不過在Cisco本身的認定上,如果使用十分複雜的密碼,LEAP仍是相當安全可靠的;但對於現實生活中的人們使用複雜的密碼,則是有其困難的一面。而較新的協定在進行MS-CHAP-V2用戶認證程序時,先行建立一個安全的傳輸層安全通道(TLS),如EAP-TTLS、PEAP,則可避免這些問題的發生。

[EAP-TLS]
EAP-TLS(Transport Layer Security)的前身為SSL(Secure Socket Layer),利用PKI來保護RADIUS的通訊。雖然EAP-TLS相當安全,但並未受到廣泛的運用,因為在無線網路環境中使用EAP-TLS,所有潛在的用戶都必須具備數位憑證;而產生以及傳遞憑證與佈置的環境中,都是相當繁雜且不容易的。

wpa_supplicant軟件設計的目標

1)與硬件和驅動不相關性
wpa_supplicant作為應用層軟件,是伴隨著IEEE802.11標準的發展以及各種硬件驅動軟件的發展和不斷完善成熟的。與硬件驅動的不相關性的設計目標也是為了在不同操作系統上的移植以及支持早期的各種wifi協議棧驅動軟件。 wpa_supplicant可以支持windows的ndis驅動,linux平台的hosap驅動,madwif驅動,ralink和atheros驅動,以及目前linux內核中主流支持的mac80211協議棧驅動。

2)OS不相關性
wpa_supplicant支持windows(包括wince),linux,BSD和Mac OS X以及嵌入式系統。

3)所有WPA功能C代碼的可移植性。

4)wpa_supplicant 是被設計成守護進程(daemon)的方式運行,就是在後台執行的程式, 通過控制接口由其他外部控制軟件(wpa_cli, wpa_gui或者其他用戶開放的軟件進行配置控制無線接入)。

總之,靈活的可移植性,可配置編譯方式可以方便地讓開發者根據需要在不同開發平台和嵌入式系統上實現WPA的安全認證功能。

wpa_supplicant軟件架構

下圖為wpa_supplicant的基本架構圖(摘自wpa_supplicant官網開發文檔圖)。

1. event loop 是核心事件處理模塊,也是wpa_supplicant的主進程程序。
從模塊名可知,它負責事件處理,處理所有的其他模塊發送過來的事件以及超時事件。並且是loop循環執行的。

2. driver i/f模塊,是負責配置控制無線驅動的API接口。
真正的功能則在wext,hosap,madwifi等接口模塊中實現。這些驅動接口模塊針對不同的OS 無線驅動的配置接口標準,或者驅動開發者自定義的配置接口。 wext是linux系統早期定義的無線配置接口標準,nl80211也是linux無線配置接口,與wext相比有些技術上的優勢(圖中未列出)。

3. driver event模塊。
wlan網絡設備驅動有些事件需要通知wpa_supplicant,讓wpa_supplicant進行下一步的處理。包括網絡連接,斷開,MIC校驗錯,關聯成功等等。該模塊的實現也是OS相關的,不同的操作系統,事件通知的機制不同。

4. L2 packet模塊。
WPA(WPA2)的安全認證是通過EAPOL幀(以太類型0x888E)來完成的。 L2 packet模塊就是完成驅動層到應用層的EAPOL的獲取和EAPOL幀的發送。 windows系統上採用NDIS協議驅動的抓包技術,wpa_supplicant的實現支持NDISUIO協議驅動(微軟提供的協議驅動)抓包,winpcap的協議驅動抓包技術。 linux系統上則採用Packet socket抓包技術。

5. Configuration模塊。
無線配置功能模塊。

6. Ctrl i/f模塊。
提高外部配置程序的控制和事件通知。

7. WPA/WPA2狀態機。
實現WPA/WPA2的密鑰協商。

8. EAPOL狀態機。
802.1x的狀態機實現。

9. EAP狀態機。
企業級安全認證實現。

Wpa_supplicant提供的接口

從通信層次上劃分,wpa_supplicant提供向上的控制接口control interface,用於與其他模塊(如UI)進行通信,其他模塊可以通過control interface來獲取信息或下發命令。
Wpa_supplicant通過socket通信機制實現下行接口,與內核進行通信,獲取信息或下發命令。

1 上行接口

Wpa_supplicant提供兩種方式的上行接口。一種基於傳統dbus機制實現與其他進程間的IPC通信;另一種通過Unix domain socket機制實現進程間的IPC通信。

1.1 Dbus接口

該接口主要在文件“ ctrl_iface_dbus.h ”,“ ctrl_iface_dbus.c ”,“ ctrl_iface_dbus_handler.h ”和“ ctrl_iface_dbus_handler.c ”中實現,提供一些基本的控制方法。

 DBusMessage * wpas_dbus_new_invalid_iface_error(DBusMessage *message);DBusMessage * wpas_dbus_global_add_interface(DBusMessage *message, struct wpa_global *global);DBusMessage * wpas_dbus_global_remove_interface(DBusMessage *message, struct wpa_global *global);DBusMessage * wpas_dbus_global_get_interface(DBusMessage *message, struct wpa_global *global);DBusMessage * wpas_dbus_global_set_debugparams(DBusMessage *message, struct wpa_global *global);DBusMessage * wpas_dbus_iface_scan(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_scan_results(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_bssid_properties(DBusMessage *message, struct wpa_supplicant *wpa_s, struct wpa_scan_res *res);DBusMessage * wpas_dbus_iface_capabilities(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_add_network(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_remove_network(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_set_network(DBusMessage *message, struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid);DBusMessage * wpas_dbus_iface_enable_network(DBusMessage *message, struct wpa_supplicant *wpa_s,  struct wpa_ssid *ssid);DBusMessage * wpas_dbus_iface_disable_network(DBusMessage *message, struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid);DBusMessage * wpas_dbus_iface_select_network(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_disconnect(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_set_ap_scan(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_set_smartcard_modules( DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_get_state(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_get_scanning(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_set_blobs(DBusMessage *message, struct wpa_supplicant *wpa_s);DBusMessage * wpas_dbus_iface_remove_blobs(DBusMessage *message, struct wpa_supplicant *wpa_s);

1.2 Unix domain socket接口
該接口主要在文件“ wpa_ctrl.h ”,“ wpa_ctrl.c ”,“ ctrl_iface_unix.c ”,“ ctrl_iface.h ”和“ ctrl_iface.c ”實現。

(1) “ wpa_ctrl.h ”,“ wpa_ctrl.c ”完成對control interface的封裝,對外提供統一的接口。
其主要的工作是通過Unix domain socket建立一個control interface的client結點,與作為server的wpa_supplicant結點通信。

主要功能函數:

struct wpa_ctrl * wpa_ctrl_open (const char *ctrl_path);/*建立並初始化一個Unix domain socket的client結點,並與作為server的wpa_supplicant結點綁定*/void wpa_ctrl_close (struct wpa_ctrl *ctrl);/*撤銷並銷毀已建立的Unix domain socket的client結點*/ int wpa_ctrl_request (struct wpa_ctrl *ctrl, const char *cmd, size_t cmd_len,                   char *reply, size_t *reply_len,                   void (*msg_cb)(char *msg, size_t len​​));/*用戶模塊直接調用該函數對wpa_supplicant發送命令並獲取所需信息 */

Note: Wpa_supplicant提供兩種由外部模塊獲取信息的方式:一種是外部模塊通過發送request命令然後獲取response的問答模式,另一種是wpa_supplicant主動向外部發送event事件,由外部模塊監聽接收。一般的常用做法是外部模塊通過調用wpa_ctrl_open()兩次,建立兩個control interface接口,一個為ctrl interface,用於發送命令,獲取信息,另一個為monitor interface,用於監聽接收來自於wpa_supplicant的event時間。此舉可以降低通信的耦合性,避免response和event的相互干擾。

int wpa_ctrl_attach (struct wpa_ctrl *ctrl); /*註冊 某個control interface作為monitor interface */ int wpa_ctrl_detach (struct wpa_ctrl *ctrl); /*撤銷某個monitor interface為 普通的control interface */ int wpa_ctrl_pending (struct wpa_ctrl *ctrl); /*判斷是否有掛起的event事件*/ int wpa_ctrl_recv (struct wpa_ctrl *ctrl, char *reply, size_t *reply_len); /*獲取掛起的event事件 */

(2) “ ctrl_iface_unix.c ”實現wpa_supplicant的Unix domain socket通信機制中server結點,完成對client結點的響應。

其中最主要的兩個函數為:

static void wpa_supplicant_ctrl_iface_receive (int sock, void *eloop_ctx, void *sock_ctx)/*接收並解析client發送request命令,然後根據不同的命令調用底層不同的處理函數; *然後將獲得response結果回饋到client結點。 */ static void wpa_supplicant_ctrl_iface_send (struct ctrl_iface_priv *priv, int level, const char *buf, size_t len​​)/*向註冊的monitor interfaces主動發送event事件*/

(3) “ ctrl_iface.h ”和“ ctrl_iface.c ”主要實現了各種request命令的底層處理函數。

2 下行接口:

wpa_supplicant與kernel(driver)進行通信, wpa_supplicant通過socket通信機制實現下行接口,與kernel(driver)進行通信,獲取信息或下發命令。
附圖說明wpa_supplicant與驅動交互的過程。
從該圖看,因為應用層部分還有WifiLayer類,說明android源碼應該是2.1的,比較老了。
不過分析了下wpa_supplicant,感覺變動不大,還是可以參考一下底層這一部分的。

wpa_supplicant下行接口主要包括三種重要的接口:

1 .PF_INET socket接口,主要用於向kernel發送ioctl命令,控制並獲取相應信息。
2 .PF_NETLINK socket接口,主要用於接收kernel發送上來的event事件。
3 .PF_PACKET socket接口,主要用於向driver傳遞802.1X報文。

主要涉及到的文件包括:
driver.h,drivers.c,driver_wext.h,driver_wext.c,l2_packet.h和l2_packet_linux.c。
其中,
– driver.h,drivers.c,driver_wext.h和driver_wext.c實現PF_INET socket接口和PF_NETLINK socket接口;
– l2_packet.h和l2_packet_linux.c實現PF_PACKET socket接口。

(1) driver.h/drivers.c:主要用於封裝底層差異,對外顯示一個相同的wpa_driver_ops接口。
wpa_supplicant可支持atmel, Broadcom, ipw, madwifi, ndis, nl80211, wext等多種驅動。
其中一個最主要的數據結構為wpa_driver_ops, 其定義了driver相關的各種操作接口。

/** * struct wpa_driver_ops - Driver interface API definition * * This structure defines the API that each driver interface needs to implement * for core wpa_supplicant code. All driver specific functionality is captured * in this wrapper. */struct wpa_driver_ops {	/** Name of the driver interface */	const char *name;	/** One line description of the driver interface */	const char *desc;	/**	 * get_bssid - Get the current BSSID	 * @priv: private driver interface data	 * @bssid: buffer for BSSID (ETH_ALEN = 6 bytes)	 *	 * Returns: 0 on success, -1 on failure	 *	 * Query kernel driver for the current BSSID and copy it to bssid.	 * Setting bssid to 00:00:00:00:00:00 is recommended if the STA is not	 * associated.	 */	int (*get_bssid)(void *priv, u8 *bssid);		.... (太長了!! 省略)...};

(2) driver_nl80211.c實現了nl80211形式的wpa_driver_ops

並創建了PF_INET socket接口,然後通過這接口完成與kernel的信息交互。
nl80211提供的一個主要資料結構為:

struct wpa_driver_nl80211_data {	struct nl80211_global *global;	struct dl_list list;	struct dl_list wiphy_list;	char phyname[32];	void *ctx;	int ifindex;	int if_removed;	int if_disabled;	int ignore_if_down_event;	struct rfkill_data *rfkill;	struct wpa_driver_capa capa;	u8 *extended_capa, *extended_capa_mask;	unsigned int extended_capa_len;	int has_capability;	int operstate;	int scan_complete_events;	struct nl_cb *nl_cb;	u8 auth_bssid[ETH_ALEN];	u8 auth_attempt_bssid[ETH_ALEN];	u8 bssid[ETH_ALEN];	u8 prev_bssid[ETH_ALEN];	int associated;	u8 ssid[32];	size_t ssid_len;	enum nl80211_iftype nlmode;	enum nl80211_iftype ap_scan_as_station;	unsigned int assoc_freq;	int monitor_sock;	int monitor_ifidx;	int monitor_refcount;	unsigned int disabled_11b_rates:1;	unsigned int pending_remain_on_chan:1;	unsigned int in_interface_list:1;	unsigned int device_ap_sme:1;	unsigned int poll_command_supported:1;	unsigned int data_tx_status:1;	unsigned int scan_for_auth:1;	unsigned int retry_auth:1;	unsigned int use_monitor:1;	unsigned int ignore_next_local_disconnect:1;	unsigned int allow_p2p_device:1;	u64 remain_on_chan_cookie;	u64 send_action_cookie;	unsigned int last_mgmt_freq;	struct wpa_driver_scan_filter *filter_ssids;	size_t num_filter_ssids;	struct i802_bss *first_bss;	int eapol_tx_sock;#ifdef HOSTAPD	int eapol_sock; /* socket for EAPOL frames */	int default_if_indices[16];	int *if_indices;	int num_if_indices;	int last_freq;	int last_freq_ht;#endif /* HOSTAPD */	/* From failed authentication command */	int auth_freq;	u8 auth_bssid_[ETH_ALEN];	u8 auth_ssid[32];	size_t auth_ssid_len;	int auth_alg;	u8 *auth_ie;	size_t auth_ie_len;	u8 auth_wep_key[4][16];	size_t auth_wep_key_len[4];	int auth_wep_tx_keyidx;	int auth_local_state_change;	int auth_p2p;};

裡面有一個 struct nl80211_global *global;
看一下定義如下

struct nl80211_global {	struct dl_list interfaces;	int if_add_ifindex;	u64 if_add_wdevid;	int if_add_wdevid_set;	struct netlink_data *netlink;	struct nl_cb *nl_cb;	struct nl_handle *nl;	int nl80211_id;	int ioctl_sock; /* socket for ioctl() use */  // PF_INET socket接口	struct nl_handle *nl_event;};

初始化

static void * nl80211_global_init(void){	struct nl80211_global *global;	struct netlink_config *cfg;	global = os_zalloc(sizeof(*global));	if (global == NULL)		return NULL;	global->ioctl_sock = -1;	dl_list_init(&global->interfaces);	global->if_add_ifindex = -1;	cfg = os_zalloc(sizeof(*cfg));	if (cfg == NULL)		goto err;	cfg->ctx = global;	cfg->newlink_cb = wpa_driver_nl80211_event_rtm_newlink;	cfg->dellink_cb = wpa_driver_nl80211_event_rtm_dellink;	global->netlink = netlink_init(cfg);	if (global->netlink == NULL) {		os_free(cfg);		goto err;	}	if (wpa_driver_nl80211_init_nl_global(global) < 0)		goto err;	global->ioctl_sock = socket(PF_INET, SOCK_DGRAM, 0);  //建立 PF_INET 的 socket	if (global->ioctl_sock < 0) {		perror("socket(PF_INET,SOCK_DGRAM)");		goto err;	}	return global;err:	nl80211_global_deinit(global);	return NULL;}

將各函式包進wpa_driver_ops類型資料結構 wpa_driver_nl80211_ops裡面
這樣對wpa_supplicant較上層的部分, 可以統一呼叫的API
只要這裡有對應不同平台的function call即可

const struct wpa_driver_ops wpa_driver_nl80211_ops = {	.name = "nl80211",	.desc = "Linux nl80211/cfg80211",	.get_bssid = wpa_driver_nl80211_get_bssid,	.get_ssid = wpa_driver_nl80211_get_ssid,	.set_key = driver_nl80211_set_key,	.scan2 = driver_nl80211_scan2,	.sched_scan = wpa_driver_nl80211_sched_scan,	.stop_sched_scan = wpa_driver_nl80211_stop_sched_scan,	.get_scan_results2 = wpa_driver_nl80211_get_scan_results,	.deauthenticate = driver_nl80211_deauthenticate,	.authenticate = driver_nl80211_authenticate,	.associate = wpa_driver_nl80211_associate,	.global_init = nl80211_global_init,	.global_deinit = nl80211_global_deinit,	.init2 = wpa_driver_nl80211_init,	.deinit = driver_nl80211_deinit,	.get_capa = wpa_driver_nl80211_get_capa,	.set_operstate = wpa_driver_nl80211_set_operstate,	.set_supp_port = wpa_driver_nl80211_set_supp_port,	.set_country = wpa_driver_nl80211_set_country,	.get_country = wpa_driver_nl80211_get_country,	.set_ap = wpa_driver_nl80211_set_ap,	.set_acl = wpa_driver_nl80211_set_acl,	.if_add = wpa_driver_nl80211_if_add,	.if_remove = driver_nl80211_if_remove,	.send_mlme = driver_nl80211_send_mlme,	.get_hw_feature_data = wpa_driver_nl80211_get_hw_feature_data,	.sta_add = wpa_driver_nl80211_sta_add,	.sta_remove = driver_nl80211_sta_remove,	.hapd_send_eapol = wpa_driver_nl80211_hapd_send_eapol,	.sta_set_flags = wpa_driver_nl80211_sta_set_flags,#ifdef HOSTAPD	.hapd_init = i802_init,	.hapd_deinit = i802_deinit,	.set_wds_sta = i802_set_wds_sta,#endif /* HOSTAPD */#if defined(HOSTAPD) || defined(CONFIG_AP)	.get_seqnum = i802_get_seqnum,	.flush = i802_flush,	.get_inact_sec = i802_get_inact_sec,	.sta_clear_stats = i802_sta_clear_stats,	.set_rts = i802_set_rts,	.set_frag = i802_set_frag,	.set_tx_queue_params = i802_set_tx_queue_params,	.set_sta_vlan = driver_nl80211_set_sta_vlan,	.sta_deauth = i802_sta_deauth,	.sta_disassoc = i802_sta_disassoc,#endif /* HOSTAPD || CONFIG_AP */	.read_sta_data = driver_nl80211_read_sta_data,	.set_freq = i802_set_freq,	.send_action = driver_nl80211_send_action,	.send_action_cancel_wait = wpa_driver_nl80211_send_action_cancel_wait,	.remain_on_channel = wpa_driver_nl80211_remain_on_channel,	.cancel_remain_on_channel =	wpa_driver_nl80211_cancel_remain_on_channel,	.probe_req_report = driver_nl80211_probe_req_report,	.deinit_ap = wpa_driver_nl80211_deinit_ap,	.deinit_p2p_cli = wpa_driver_nl80211_deinit_p2p_cli,	.resume = wpa_driver_nl80211_resume,	.send_ft_action = nl80211_send_ft_action,	.signal_monitor = nl80211_signal_monitor,	.signal_poll = nl80211_signal_poll,	.send_frame = nl80211_send_frame,	.shared_freq = wpa_driver_nl80211_shared_freq,	.set_param = nl80211_set_param,	.get_radio_name = nl80211_get_radio_name,	.add_pmkid = nl80211_add_pmkid,	.remove_pmkid = nl80211_remove_pmkid,	.flush_pmkid = nl80211_flush_pmkid,	.set_rekey_info = nl80211_set_rekey_info,	.poll_client = nl80211_poll_client,	.set_p2p_powersave = nl80211_set_p2p_powersave,	.start_dfs_cac = nl80211_start_radar_detection,	.stop_ap = wpa_driver_nl80211_stop_ap,#ifdef CONFIG_TDLS	.send_tdls_mgmt = nl80211_send_tdls_mgmt,	.tdls_oper = nl80211_tdls_oper,#endif /* CONFIG_TDLS */	.update_ft_ies = wpa_driver_nl80211_update_ft_ies,	.get_mac_addr = wpa_driver_nl80211_get_macaddr,	.get_survey = wpa_driver_nl80211_get_survey,#ifdef ANDROID_P2P	.set_noa = wpa_driver_set_p2p_noa,	.get_noa = wpa_driver_get_p2p_noa,	.set_ap_wps_ie = wpa_driver_set_ap_wps_p2p_ie,#endif#ifdef ANDROID	.driver_cmd = wpa_driver_nl80211_driver_cmd,#endif};

(3) driver_wext.h/driver_wext.c實現了wext形式的wpa_driver_ops

並創建了PF_INET socket接口和PF_NETLINK socket接口(wpa_supplicant_8中沒有創建PF_NETLINK socket接口),然後通過這兩個接口完成與kernel的信息交互。

wext提供的一個主要數據結構為:

struct wpa_driver_wext_data {	void *ctx;	int event_sock;  // PF_NETLINK socket接口 (似乎沒用到)	int ioctl_sock;  // PF_INET socket接口	int mlme_sock;	char ifname[IFNAMSIZ + 1];	int ifindex;	int ifindex2;	u8 *assoc_req_ies;	size_t assoc_req_ies_len;	u8 *assoc_resp_ies;	size_t assoc_resp_ies_len;	struct wpa_driver_capa capa;	int has_capability;	int we_version_compiled;	/* for set_auth_alg fallback */	int use_crypt;	int auth_alg_fallback;	int operstate;	char mlmedev[IFNAMSIZ + 1];	int scan_complete_events;};

其中event_sock 為PF_NETLINK socket接口,ioctl_sock為PF_INET socket接口。

driver_wext.c 實現了大量底層處理函數用於實現wpa_driver_ops操作參數,其中比較重要的有:

/* 初始化wpa_driver_wext_data數據結構,並創建PF_NETLINK socket和PF_INET socket接口 */
void * wpa_driver_wext_init( void *ctx, const char * ifname);

/** * wpa_driver_wext_init - Initialize WE driver interface * @ctx: context to be used when calling wpa_supplicant functions, * e.g., wpa_supplicant_event() * @ifname: interface name, e.g., wlan0 * Returns: Pointer to private data, %NULL on failure */void * wpa_driver_wext_init(void *ctx, const char *ifname){	struct wpa_driver_wext_data *drv;	struct netlink_config *cfg;	struct rfkill_config *rcfg;	char path[128];	struct stat buf;	wpa_printf(MSG_DEBUG, "WEXT: wpa_driver_wext_init 001");	drv = os_zalloc(sizeof(*drv));	if (drv == NULL)		return NULL;	drv->ctx = ctx;	os_strlcpy(drv->ifname, ifname, sizeof(drv->ifname));	os_snprintf(path, sizeof(path), "/sys/class/net/%s/phy80211", ifname);	if (stat(path, &buf) == 0) {		wpa_printf(MSG_DEBUG, "WEXT: cfg80211-based driver detected");		drv->cfg80211 = 1;		wext_get_phy_name(drv);	}	wpa_printf(MSG_DEBUG, "WEXT: wpa_driver_wext_init 002");	drv->ioctl_sock = socket(PF_INET, SOCK_DGRAM, 0);	if (drv->ioctl_sock < 0) { 		perror("socket(PF_INET,SOCK_DGRAM)"); 		goto err1; 	} 	cfg = os_zalloc(sizeof(*cfg)); 	if (cfg == NULL) 		goto err1; 	cfg->ctx = drv;	cfg->newlink_cb = wpa_driver_wext_event_rtm_newlink;	cfg->dellink_cb = wpa_driver_wext_event_rtm_dellink;	drv->netlink = netlink_init(cfg);	if (drv->netlink == NULL) {		os_free(cfg);		goto err2;	}	wpa_printf(MSG_DEBUG, "WEXT: wpa_driver_wext_init 003");	rcfg = os_zalloc(sizeof(*rcfg));	if (rcfg == NULL)		goto err3;	rcfg->ctx = drv;	os_strlcpy(rcfg->ifname, ifname, sizeof(rcfg->ifname));	rcfg->blocked_cb = wpa_driver_wext_rfkill_blocked;	rcfg->unblocked_cb = wpa_driver_wext_rfkill_unblocked;	drv->rfkill = rfkill_init(rcfg);	if (drv->rfkill == NULL) {		wpa_printf(MSG_DEBUG, "WEXT: RFKILL status not available");		os_free(rcfg);	}	drv->mlme_sock = -1;#ifdef ANDROID	drv->errors = 0;	drv->driver_is_started = TRUE;	drv->bgscan_enabled = 0;#endif /* ANDROID */	wpa_printf(MSG_DEBUG, "WEXT: wpa_driver_wext_init 005");	if (wpa_driver_wext_finish_drv_init(drv) < 0) 		goto err3; 	wpa_driver_wext_set_auth_param(drv, IW_AUTH_WPA_ENABLED, 1); 	wpa_printf(MSG_DEBUG, "WEXT: wpa_driver_wext_init 006"); 	return drv; err3: 	rfkill_deinit(drv->rfkill);	netlink_deinit(drv->netlink);err2:	close(drv->ioctl_sock);err1:	os_free(drv);	return NULL;}

/* 銷毀wpa_driver_wext_data數據結構,PF_NETLINK socket和PF_INET socket接口 */
void wpa_driver_wext_deinit( void * priv);

 /** * wpa_driver_wext_deinit - Deinitialize WE driver interface * @priv: Pointer to private wext data from wpa_driver_wext_init() * * Shut down driver interface and processing of driver events. Free * private data buffer if one was allocated in wpa_driver_wext_init(). */void wpa_driver_wext_deinit(void *priv){	struct wpa_driver_wext_data *drv = priv;	wpa_driver_wext_set_auth_param(drv, IW_AUTH_WPA_ENABLED, 0);	eloop_cancel_timeout(wpa_driver_wext_scan_timeout, drv, drv->ctx);	/*	 * Clear possibly configured driver parameters in order to make it	 * easier to use the driver after wpa_supplicant has been terminated.	 */	wpa_driver_wext_disconnect(drv);	netlink_send_oper_ifla(drv->netlink, drv->ifindex, 0, IF_OPER_UP);	netlink_deinit(drv->netlink);	rfkill_deinit(drv->rfkill);	if (drv->mlme_sock >= 0)		eloop_unregister_read_sock(drv->mlme_sock);	(void) linux_set_iface_flags(drv->ioctl_sock, drv->ifname, 0);	close(drv->ioctl_sock);	if (drv->mlme_sock >= 0)		close(drv->mlme_sock);	os_free(drv->assoc_req_ies);	os_free(drv->assoc_resp_ies);	os_free(drv);}

最後,將實現的操作函數映射到一個全局的wpa_driver_ops類型數據結構wpa_driver_wext_ops中。

const struct wpa_driver_ops wpa_driver_wext_ops = {	.name = "wext",	.desc = "Linux wireless extensions (generic)",	.get_bssid = wpa_driver_wext_get_bssid,	.get_ssid = wpa_driver_wext_get_ssid,	.set_key = wpa_driver_wext_set_key,	.set_countermeasures = wpa_driver_wext_set_countermeasures,	.scan2 = wpa_driver_wext_scan,	.get_scan_results2 = wpa_driver_wext_get_scan_results,	.deauthenticate = wpa_driver_wext_deauthenticate,	.associate = wpa_driver_wext_associate,	.init = wpa_driver_wext_init,	.deinit = wpa_driver_wext_deinit,	.add_pmkid = wpa_driver_wext_add_pmkid,	.remove_pmkid = wpa_driver_wext_remove_pmkid,	.flush_pmkid = wpa_driver_wext_flush_pmkid,	.get_capa = wpa_driver_wext_get_capa,	.set_operstate = wpa_driver_wext_set_operstate,	.get_radio_name = wext_get_radio_name,#ifdef ANDROID	.sched_scan = wext_sched_scan,	.stop_sched_scan = wext_stop_sched_scan,#endif /* ANDROID */};

(3) l2_packet.h/l2_packet_linux.c 主要用於實現PF_PACKET socket接口,
通過該接口,wpa_supplicant可以直接將802.1X packet發送到L2層,而不經過TCP/IP協議棧。

其中主要的功能函數為:

/*  創建並初始化PF_PACKET socket接口,其中rx_callback為從L2接收到的packet處理callback函數  */ struct l2_packet_data * l2_packet_init(        const  char *ifname, const u8 *own_addr, unsigned short protocol,        void (*rx_callback)( void * ctx, const u8 * src_addr,                          const u8 * buf, size_t len),        void *rx_callback_ctx, int l2_hdr);

ps:l2_packet_init方法中有代碼:

eloop_register_read_sock(l2->fd, l2_packet_receive, l2, NULL);

即註冊了與socket關聯的回調方法為l2_packet_receive。

/*  銷毀PF_PACKET socket接口  */ void l2_packet_deinit( struct l2_packet_data * l2); /* L2層packet發送函數,wpa_supplicant用此發送L2層802.1X packet   */ int l2_packet_send( struct l2_packet_data *l2, const u8 * dst_addr, u16 proto,                  const u8 * buf, size_t len); /*   L2層packet接收函數,接收來自L2層數據後,將其發送到上層   */ static  void l2_packet_receive( int sock, void *eloop_ctx, void *sock_ctx);

關於 L2_packet 模塊

L2_packet模塊是wpa_supplicant軟件中實現EAPOL幀(frame)的收發功能的模塊。 L2即網絡協議層的數據鏈路層 layer 2 。 wpa_supplicant針對不同的OS系統,採用了不同的抓包技術實現。 windows平台採用NDIS協議驅動抓包技術,linux平台採用packet socket抓包技術。

該模塊的實現代碼在目錄wpa_supplicant/src/l2_packet中。
l2_packet_linux.c是linux系統下的收發EAPOL幀實現。
l2_packet_ndis.c是windows系統下使用ndisuio協議驅動實現收發EAPOL幀實現。
l2_packet_pcap是windows系統下使用winpcap協議驅動實現EAPOL收發,採用輪詢的方式抓包。
l2_packet_winpcap是windows系統下使用winpcap協議驅動實現EAPOL收發,採用接收線程的方式抓包。相比l2_packet_pcap抓包,接收EAPOL幀的延遲從100ms降到了3ms。
l2_packet.h是api接口聲明和struct l2_packet_data聲明頭文件。

l2_packet主要的接口函數說明

l2_packet_init()
l2_packet_init()函數在wpa_supplicant初始化時候調用。

struct l2_packet_data * l2_packet_init(      const char *ifname, const u8 *own_addr, unsigned short protocol,      void (*rx_callback)(void *ctx, const u8 *src_addr,                  const u8 *buf, size_t len),      void *rx_callback_ctx, int l2_hdr);  

ifname: 網絡設備名
own_addr: mac地址
protocol:協議類型或者以太類型。如抓取EAPOL幀,以太類型為0x888E。
rx_callback: 接收到EAPOL幀的回調處理函數。
ctx:上面回調處理函數的回調參數。
l2_hrd:收發數據是否包含l2層以太頭。通常設置為0,不包含。

l2_packet_deinit
wpa_supplicant退出或清除時調用,釋放相關資源。

l2_packet_send
向驅動發送EAPOL幀(frame)接口函數。

l2_packet_notify_auth_start
該函數接口的實現只在l2_packet_winpcap中實現,因其採用創建一個線程抓包,所以在無線聯網關聯成功之後,喚醒l2_packet接收線程,準備獲取EAPOL幀。

l2_packet_linux的實現分析

在linux系統上,wpa_supplicant採用PACKET SOCKET技術抓取EAPOL幀(frame)。 wpa_supplicant的Event Loop模塊採用輪詢方式獲取EAPOL幀(frame),並處理。

初始化

struct l2_packet_data * l2_packet_init(      const char *ifname, const u8 *own_addr, unsigned short protocol,      void (*rx_callback)(void *ctx, const u8 *src_addr,                  const u8 *buf, size_t len),      void *rx_callback_ctx, int l2_hdr)  {      struct l2_packet_data *l2;      struct ifreq ifr;      struct sockaddr_ll ll;        l2 = os_zalloc(sizeof(struct l2_packet_data));      if (l2 == NULL)          return NULL;      os_strlcpy(l2->ifname, ifname, sizeof(l2->ifname));      l2->rx_callback = rx_callback;      l2->rx_callback_ctx = rx_callback_ctx;      l2->l2_hdr = l2_hdr;        l2->fd = socket(PF_PACKET, l2_hdr ? SOCK_RAW : SOCK_DGRAM,              htons(protocol));      if (l2->fd < 0) {           wpa_printf(MSG_ERROR, "%s: socket(PF_PACKET): %s",                  __func__, strerror(errno));           os_free(l2);           return NULL;       }       os_memset(&ifr, 0, sizeof(ifr));       os_strlcpy(ifr.ifr_name, l2->ifname, sizeof(ifr.ifr_name));      if (ioctl(l2->fd, SIOCGIFINDEX, &ifr) < 0) {           wpa_printf(MSG_ERROR, "%s: ioctl[SIOCGIFINDEX]: %s",                  __func__, strerror(errno));           close(l2->fd);          os_free(l2);          return NULL;      }      l2->ifindex = ifr.ifr_ifindex;        os_memset(&ll, 0, sizeof(ll));      ll.sll_family = PF_PACKET;      ll.sll_ifindex = ifr.ifr_ifindex;      ll.sll_protocol = htons(protocol);      if (bind(l2->fd, (struct sockaddr *) &ll, sizeof(ll)) < 0) {           wpa_printf(MSG_ERROR, "%s: bind[PF_PACKET]: %s",                  __func__, strerror(errno));           close(l2->fd);          os_free(l2);          return NULL;      }        if (ioctl(l2->fd, SIOCGIFHWADDR, &ifr) < 0) {           wpa_printf(MSG_ERROR, "%s: ioctl[SIOCGIFHWADDR]: %s",                  __func__, strerror(errno));           close(l2->fd);          os_free(l2);          return NULL;      }      os_memcpy(l2->own_addr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);        eloop_register_read_sock(l2->fd, l2_packet_receive, l2, NULL);        return l2;  }  

1)分配struct l2_packet_data,並設置回調函數和回調參數以及l2_hdr標記
2)調用socket創建端口文件句柄,其協議族為PF_PACKET,SOCK_TYPE根據是否包含以太幀頭設置為SOCK_RAW或者SOCK_DGRAM,發送和接收到以太幀類型為EAPOL幀。
3)調用ioctl(l2->fd, SIOCGIFINDEX, &ifr)獲取網絡設備對應的索引值。
4)配置sockaddr_ll數據結構,調用bind函數綁定端口文件句柄。
5)調用ioctl(l2->fd, SIOCGIFHWADDR, &ifr)獲取硬件MAC地址備用。
6)調用eloop_register_read_sock向wpa_supplicant的Event Loop模塊註冊一個讀取socket的操作。事件觸發的回調函數為l2_packet_receive。回調函數上下文參數為l2。

Event Loop 模塊如何調用 l2_packet_receive ?

在l2_packet_init()函數中調用了eloop_register_read_sock註冊了一個讀socket操作。

在Event Loop模塊的eloop_run()函數中
循環執行調用 eloop_sock_table_dispatch(&eloop.readers, rfds) ,對所有註冊的讀取socket的操作進行處理,
通過調用FD_ISSET(table->table[i].sock, fds )判斷socket文件句柄的狀態是否發生變化,如果發生變化,則調用註冊的回調函數(即l2_packet_receive)
在l2_packet_init()註冊: eloop_register_read_sock(l2->fd, l2_packet_receive, l2, NULL);

void eloop_run(void){#ifdef CONFIG_ELOOP_POLL	...#else /* CONFIG_ELOOP_POLL */	fd_set *rfds, *wfds, *efds;	struct timeval _tv;#endif /* CONFIG_ELOOP_POLL */	int res;	struct os_time tv, now;#ifndef CONFIG_ELOOP_POLL	...#endif /* CONFIG_ELOOP_POLL */	while (!eloop.terminate &&	       (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 || eloop.writers.count > 0 || eloop.exceptions.count > 0)) {		struct eloop_timeout *timeout;		timeout = dl_list_first(&eloop.timeout, struct eloop_timeout, list);		if (timeout) {			os_get_time(&now);			if (os_time_before(&now, &timeout->time))				os_time_sub(&timeout->time, &now, &tv);			else				tv.sec = tv.usec = 0;#ifdef CONFIG_ELOOP_POLL			...#else /* CONFIG_ELOOP_POLL */			_tv.tv_sec = tv.sec;			_tv.tv_usec = tv.usec;#endif /* CONFIG_ELOOP_POLL */		}#ifdef CONFIG_ELOOP_POLL		...#else /* CONFIG_ELOOP_POLL */		eloop_sock_table_set_fds(&eloop.readers, rfds);		eloop_sock_table_set_fds(&eloop.writers, wfds);		eloop_sock_table_set_fds(&eloop.exceptions, efds);		res = select(eloop.max_sock + 1, rfds, wfds, efds,			     timeout ? &_tv : NULL);		if (res < 0 && errno != EINTR && errno != 0) {			perror("select");			goto out;		}#endif /* CONFIG_ELOOP_POLL */		eloop_process_pending_signals();		/* check if some registered timeouts have occurred */		timeout = dl_list_first(&eloop.timeout, struct eloop_timeout, list);		if (timeout) {			os_get_time(&now);			if (!os_time_before(&now, &timeout->time)) {				void *eloop_data = timeout->eloop_data;				void *user_data = timeout->user_data;				eloop_timeout_handler handler = timeout->handler;				eloop_remove_timeout(timeout);				handler(eloop_data, user_data);			}		}		if (res <= 0)			continue;#ifdef CONFIG_ELOOP_POLL		...#else /* CONFIG_ELOOP_POLL */		eloop_sock_table_dispatch(&eloop.readers, rfds); //循環執行調用 eloop_sock_table_dispatch(&eloop.readers, rfds) ,對所有註冊的讀取socket的操作進行處理		eloop_sock_table_dispatch(&eloop.writers, wfds);		eloop_sock_table_dispatch(&eloop.exceptions, efds);#endif /* CONFIG_ELOOP_POLL */	}	eloop.terminate = 0;out:#ifndef CONFIG_ELOOP_POLL	os_free(rfds);	os_free(wfds);	os_free(efds);#endif /* CONFIG_ELOOP_POLL */	return;}
static void eloop_sock_table_dispatch(struct eloop_sock_table *table, fd_set *fds){	int i;	if (table == NULL || table->table == NULL)		return;	table->changed = 0;	for (i = 0; i < table->count; i++) {		//通過調用FD_ISSET(table->table[i].sock, fds )判斷socket文件句柄的狀態是否發生變化                if (FD_ISSET(table->table[i].sock, fds)) {                         //如果發生變化,則調用註冊的回調函數(即l2_packet_receive)			table->table[i].handler(table->table[i].sock, table->table[i].eloop_data, table->table[i].user_data); 			if (table->changed)				break;		}	}}

EAPOL帧frame的接收

static void l2_packet_receive(int sock, void *eloop_ctx, void *sock_ctx)  {      struct l2_packet_data *l2 = eloop_ctx;      u8 buf[2300];      int res;      struct sockaddr_ll ll;      socklen_t fromlen;        os_memset(&ll, 0, sizeof(ll));      fromlen = sizeof(ll);      res = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *) &ll,  &fromlen);      if (res < 0) {           wpa_printf(MSG_DEBUG, "l2_packet_receive - recvfrom: %s", strerror(errno));           return;     }     l2->rx_callback(l2->rx_callback_ctx, ll.sll_addr, buf, res);  }  

1)調用recvfrom把接收到的EAPOL幀拷貝到buf緩存中(最長2300字節)。
2)調用註冊的EAPOL接收處理函數,即wpa_supplicant_rx_eapol()。(註冊在wpa_supplicant_update_mac_addr()裡, 帶入參數去呼叫l2_packet_init(wpa_s->ifname, wpa_drv_get_mac_addr(wpa_s), ETH_P_EAPOL, wpa_supplicant_rx_eapol, wpa_s, 0);)

EAPOL帧frame的發送

int l2_packet_send(struct l2_packet_data *l2, const u8 *dst_addr, u16 proto, const u8 *buf, size_t len)  {      int ret;      if (l2 == NULL)          return -1;      if (l2->l2_hdr) {          ret = send(l2->fd, buf, len, 0);          if (ret < 0)               wpa_printf(MSG_ERROR, "l2_packet_send - send: %s", strerror(errno));     } else {        struct sockaddr_ll ll;        os_memset(&ll, 0, sizeof(ll));        ll.sll_family = AF_PACKET;        ll.sll_ifindex = l2->ifindex;          ll.sll_protocol = htons(proto);          ll.sll_halen = ETH_ALEN;          os_memcpy(ll.sll_addr, dst_addr, ETH_ALEN);          ret = sendto(l2->fd, buf, len, 0, (struct sockaddr *) &ll, sizeof(ll));          if (ret < 0) {              wpa_printf(MSG_ERROR, "l2_packet_send - sendto: %s", strerror(errno));          }      }      return ret;  }  

1)根據l2_hdr標記,如果l2_hdr配置為1,則直接發送原始數據。如果l2_hdr配置為0,則需要添加Ehernet frame header進行發送。
2)根據兩種情況,分別調用send(RAW) 或者sendto(DGRAM)發送EAPOL幀。

參考:

wpa_supplicant下行接口浅析
wpa_supplicant 2.0版源代码阅读(2) —- L2_packet模块

2014/12/08

sk_buff 定義及其操作 (封包,數據包,packet ...)

(出處: 333)

1. sk_buff 結構體
可以看出sk_buff 結構體很重要,
sk_buff — 套接字緩衝區,用來在linux網絡子系統中各層之間數據傳遞,起到了“神經中樞”的作用。
當發送數據包時,linux內核的網絡模塊必須建立一個包含要傳輸的數據包的sk_buff,然後將sk_buff傳遞給下一層,各層在sk_buff 中添加不同的協議頭,直到交給網絡設備發送。同樣,當接收數據包時,網絡設備從物理媒介層接收到數據後,他必須將接收到的數據轉換為sk_buff,並傳遞給上層,各層剝去相應的協議頭後直到交給用戶。
sk_buff結構如下圖所示: (define at include/linux/skbuff.h)

sk_buff定義如​​下:

/**  *	struct sk_buff - socket buffer *	@next: Next buffer in list *	@prev: Previous buffer in list *	@tstamp: Time we arrived *	@sk: Socket we are owned by *	@dev: Device we arrived on/are leaving by *	@cb: Control buffer. Free for use by every layer. Put private vars here *	@_skb_refdst: destination entry (with norefcount bit) *	@sp: the security path, used for xfrm *	@len: Length of actual data *	@data_len: Data length *	@mac_len: Length of link layer header *	@hdr_len: writable header length of cloned skb *	@csum: Checksum (must include start/offset pair) *	@csum_start: Offset from skb->head where checksumming should start *	@csum_offset: Offset from csum_start where checksum should be stored *	@priority: Packet queueing priority *	@local_df: allow local fragmentation *	@cloned: Head may be cloned (check refcnt to be sure) *	@ip_summed: Driver fed us an IP checksum *	@nohdr: Payload reference only, must not modify header *	@nfctinfo: Relationship of this skb to the connection *	@pkt_type: Packet class *	@fclone: skbuff clone status *	@ipvs_property: skbuff is owned by ipvs *	@peeked: this packet has been seen already, so stats have been *		done for it, don't do them again *	@nf_trace: netfilter packet trace flag *	@protocol: Packet protocol from driver *	@destructor: Destruct function *	@nfct: Associated connection, if any *	@nfct_reasm: netfilter conntrack re-assembly pointer *	@nf_bridge: Saved data about a bridged frame - see br_netfilter.c *	@skb_iif: ifindex of device we arrived on *	@tc_index: Traffic control index *	@tc_verd: traffic control verdict *	@rxhash: the packet hash computed on receive *	@queue_mapping: Queue mapping for multiqueue devices *	@ndisc_nodetype: router type (from link layer) *	@ooo_okay: allow the mapping of a socket to a queue to be changed *	@l4_rxhash: indicate rxhash is a canonical 4-tuple hash over transport *		ports. *	@wifi_acked_valid: wifi_acked was set *	@wifi_acked: whether frame was acked on wifi or not *	@no_fcs:  Request NIC to treat last 4 bytes as Ethernet FCS *	@dma_cookie: a cookie to one of several possible DMA operations *		done by skb DMA functions *	@secmark: security marking *	@mark: Generic packet mark *	@dropcount: total number of sk_receive_queue overflows *	@vlan_tci: vlan tag control information *	@transport_header: Transport layer header *	@network_header: Network layer header *	@mac_header: Link layer header *	@tail: Tail pointer *	@end: End pointer *	@head: Head of buffer *	@data: Data head pointer *	@truesize: Buffer size *	@users: User count - see {datagram,tcp}.c */struct sk_buff {	/* These two members must be first. */	struct sk_buff		*next;	struct sk_buff		*prev;	ktime_t			tstamp;	struct sock		*sk;	struct net_device	*dev;	/*	 * 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] __aligned(8);	unsigned long		_skb_refdst;#ifdef CONFIG_XFRM	struct	sec_path	*sp;#endif	unsigned int		len,				data_len;	__u16			mac_len,				hdr_len;	union {		__wsum		csum;		struct {			__u16	csum_start;			__u16	csum_offset;		};	};	__u32			priority;	kmemcheck_bitfield_begin(flags1);	__u8			local_df:1,				cloned:1,				ip_summed:2,				nohdr:1,				nfctinfo:3;	__u8			pkt_type:3,				fclone:2,				ipvs_property:1,				peeked:1,				nf_trace:1;	kmemcheck_bitfield_end(flags1);	__be16			protocol;	void			(*destructor)(struct sk_buff *skb);#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)	struct nf_conntrack	*nfct;#endif#ifdef NET_SKBUFF_NF_DEFRAG_NEEDED	struct sk_buff		*nfct_reasm;#endif#ifdef CONFIG_BRIDGE_NETFILTER	struct nf_bridge_info	*nf_bridge;#endif	int			skb_iif;	__u32			rxhash;	__u16			vlan_tci;#ifdef CONFIG_NET_SCHED	__u16			tc_index;	/* traffic control index */#ifdef CONFIG_NET_CLS_ACT	__u16			tc_verd;	/* traffic control verdict */#endif#endif	__u16			queue_mapping;	kmemcheck_bitfield_begin(flags2);#ifdef CONFIG_IPV6_NDISC_NODETYPE	__u8			ndisc_nodetype:2;#endif	__u8			ooo_okay:1;	__u8			l4_rxhash:1;	__u8			wifi_acked_valid:1;	__u8			wifi_acked:1;	__u8			no_fcs:1;	/* 9/11 bit hole (depending on ndisc_nodetype presence) */	kmemcheck_bitfield_end(flags2);#ifdef CONFIG_NET_DMA	dma_cookie_t		dma_cookie;#endif#ifdef CONFIG_NETWORK_SECMARK	__u32			secmark;#endif	union {		__u32		mark;		__u32		dropcount;		__u32		avail_size;	};	sk_buff_data_t		transport_header;	sk_buff_data_t		network_header;	sk_buff_data_t		mac_header;	/* These elements must be at the end, see alloc_skb() for details.  */	sk_buff_data_t		tail;	sk_buff_data_t		end;	unsigned char		*head,				*data;	unsigned int		truesize;	atomic_t		users;};

sk_buff主要成員如下:
1.1 各層協議頭:
— transport_header : 傳輸層協議頭,如TCP, UDP , ICMP, IGMP等協議頭
— network_header : 網絡層協議頭, 如IP, IPv6, ARP 協議頭
— mac_header : 鏈路層協議頭。
— sk_buff_data_t 原型就是一個char 指針

#ifdef NET_SKBUFF_DATA_USES_OFFSETtypedef unsigned int sk_buff_data_t;#elsetypedef unsigned char *sk_buff_data_t;#endif

1.2 數據緩衝區指針head, data, tail, end
— *head :指向內存中已分配的用於存放網絡數據緩衝區的起始地址, sk_buff和相關數據被分配後,該指針值就固定了
— *data : 指向對應當前協議層有效數據的起始地址。
每個協議層的有效數據內容不一樣,各層有效數據的內容如下:
a. 對於傳輸層,有效數據包括用戶數據和傳輸層協議頭
b. 對於網絡層,有效數據包括用戶數據、傳輸層協議和網絡層協議頭。
c. 對於數據鏈路層,有效數據包括用戶數據、傳輸層協議、網絡層協議和鏈路層協議。
因此,data指針隨著當前擁有sk_buff的協議層的變化而進行相應的移動。
— tail :指向對應當前協議層有效數據的結尾地址,與data指針相對應。
— end :指向內存中分配的網絡數據緩衝區的結尾,與head指針相對應。和head一樣,sk_buff被分配後,end指針就固定了。
head, data, tail, end 關係如下圖所示:

1.3 長度信息len, data_len, truesize
— len :指網絡數據包的有效數據的長度,包括協議頭和負載(payload).
— data_len : 記錄分片的數據長度
— truesize :表述緩存區的整體長度,一般為sizeof(sk_buff).
1.4 數據包類型
— pkt_type :指定數據包類型。驅動程序負責將其設置為:
PACKET_HOST — 該數據包是給我的。
PACKET_OTHERHOST — 該數據包不是給我的。
PACKET_BROADCAST — 廣播類型的數據包
PACKET_MULTICAST — 組播類型的數據包
驅動程序不必顯式的修改pkt_type,因為eth_type_trans會完成該工作。
2. 套接字緩衝區的操作
2.1 分配套接字緩衝區
struct sk_buff *alloc_skb(unsigned intlen, int priority);
alloc_skb()函數分配一個套接字緩衝區和一個數據緩衝區。
— len : 為數據緩衝區的大小
— priority : 內存分配的優先級

static inline struct sk_buff *alloc_skb(unsigned int size,					gfp_t priority){	return __alloc_skb(size, priority, 0, NUMA_NO_NODE);}
/** *	__alloc_skb	-	allocate a network buffer *	@size: size to allocate *	@gfp_mask: allocation mask *	@fclone: allocate from fclone cache instead of head cache *		and allocate a cloned (child) skb *	@node: numa node to allocate memory on * *	Allocate a new &sk_buff. The returned buffer has no headroom and a *	tail room of size bytes. The object has a reference count of one. *	The return is the buffer. On a failure the return is %NULL. * *	Buffers may only be allocated from interrupts using a @gfp_mask of *	%GFP_ATOMIC. */struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,			    int fclone, int node){	struct kmem_cache *cache;	struct skb_shared_info *shinfo;	struct sk_buff *skb;	u8 *data;	cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;	/* Get the HEAD */	skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);	if (!skb)		goto out;	prefetchw(skb);	/* We do our best to align skb_shared_info on a separate cache	 * line. It usually works because kmalloc(X > SMP_CACHE_BYTES) gives	 * aligned memory blocks, unless SLUB/SLAB debug is enabled.	 * Both skb->head and skb_shared_info are cache line aligned.	 */	size = SKB_DATA_ALIGN(size);	size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));	data = kmalloc_node_track_caller(size, gfp_mask, node);	if (unlikely(ZERO_OR_NULL_PTR(data)))		goto nodata;	/* kmalloc(size) might give us more room than requested.	 * Put skb_shared_info exactly at the end of allocated zone,	 * to allow max possible filling before reallocation.	 */	size = SKB_WITH_OVERHEAD(ksize(data));	prefetchw(data + size);	/*	 * Only clear those fields we need to clear, not those that we will	 * actually initialise below. Hence, don't put any more fields after	 * the tail pointer in struct sk_buff!	 */	memset(skb, 0, offsetof(struct sk_buff, tail));	/* Account for allocated memory : skb + skb->head */	skb->truesize = SKB_TRUESIZE(size);	atomic_set(&skb->users, 1);	skb->head = data;	skb->data = data;	skb_reset_tail_pointer(skb);	skb->end = skb->tail + size;#ifdef NET_SKBUFF_DATA_USES_OFFSET	skb->mac_header = ~0U;#endif	/* make sure we initialize shinfo sequentially */	shinfo = skb_shinfo(skb);	memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));	atomic_set(&shinfo->dataref, 1);	kmemcheck_annotate_variable(shinfo->destructor_arg);	if (fclone) {		struct sk_buff *child = skb + 1;		atomic_t *fclone_ref = (atomic_t *) (child + 1);		kmemcheck_annotate_bitfield(child, flags1);		kmemcheck_annotate_bitfield(child, flags2);		skb->fclone = SKB_FCLONE_ORIG;		atomic_set(fclone_ref, 1);		child->fclone = SKB_FCLONE_UNAVAILABLE;	}out:	return skb;nodata:	kmem_cache_free(cache, skb);	skb = NULL;	goto out;}EXPORT_SYMBOL(__alloc_skb);

struct sk_buff *dev_alloc_skb(unsignedint len​​);
dev_alloc_skb()函數以GFP_ATOMIC 優先級調用上面的alloc_skb()函數。
並保存skb->dead 和skb->data之間的16個字節

/** *	dev_alloc_skb - allocate an skbuff for receiving *	@length: length to allocate * *	Allocate a new &sk_buff and assign it a usage count of one. The *	buffer has unspecified headroom built in. Users should allocate *	the headroom they think they need without accounting for the *	built in space. The built in space is used for optimisations. * *	%NULL is returned if there is no free memory. Although this function *	allocates memory it can be called from an interrupt. */struct sk_buff *dev_alloc_skb(unsigned int length){	/*	 * There is more code here than it seems:	 * __dev_alloc_skb is an inline	 */	return __dev_alloc_skb(length, GFP_ATOMIC);}EXPORT_SYMBOL(dev_alloc_skb);
/** *	__dev_alloc_skb - allocate an skbuff for receiving *	@length: length to allocate *	@gfp_mask: get_free_pages mask, passed to alloc_skb * *	Allocate a new &sk_buff and assign it a usage count of one. The *	buffer has unspecified headroom built in. Users should allocate *	the headroom they think they need without accounting for the *	built in space. The built in space is used for optimisations. * *	%NULL is returned if there is no free memory. */static inline struct sk_buff *__dev_alloc_skb(unsigned int length,					      gfp_t gfp_mask){	struct sk_buff *skb = alloc_skb(length + NET_SKB_PAD, gfp_mask);	if (likely(skb))		skb_reserve(skb, NET_SKB_PAD);	return skb;}

2.2 釋放套接字緩衝區
void kfree_skb(struct sk_buff *skb);

/** *	kfree_skb - free an sk_buff *	@skb: buffer to free * *	Drop a reference to the buffer and free it if the usage count has *	hit zero. */void kfree_skb(struct sk_buff *skb){	if (unlikely(!skb))		return;	if (likely(atomic_read(&skb->users) == 1))		smp_rmb();	else if (likely(!atomic_dec_and_test(&skb->users)))		return;	trace_kfree_skb(skb, __builtin_return_address(0));	__kfree_skb(skb);}EXPORT_SYMBOL(kfree_skb);

— kfree_skb() 函數只能在內核內部使用,網絡設備驅動中必須使用dev_kfree_skb()、dev_kfree_skb_irq() 或dev_kfree_skb_any().
void dev_kfree_skb(struct sk_buff *skb);
— dev_kfree_skb()用於非中斷上下文。

#define dev_kfree_skb(a)	consume_skb(a)
/** *	consume_skb - free an skbuff *	@skb: buffer to free * *	Drop a ref to the buffer and free it if the usage count has hit zero *	Functions identically to kfree_skb, but kfree_skb assumes that the frame *	is being dropped after a failure and notes that */void consume_skb(struct sk_buff *skb){	if (unlikely(!skb))		return;	if (likely(atomic_read(&skb->users) == 1))		smp_rmb();	else if (likely(!atomic_dec_and_test(&skb->users)))		return;	trace_consume_skb(skb);	__kfree_skb(skb);}EXPORT_SYMBOL(consume_skb);

void dev_kfree_skb_irq(struct sk_buff *skb);
— dev_kfree_skb_irq() 用於中斷上下文。

void dev_kfree_skb_irq(struct sk_buff *skb){	if (atomic_dec_and_test(&skb->users)) {		struct softnet_data *sd;		unsigned long flags;		local_irq_save(flags);		sd = &__get_cpu_var(softnet_data);		skb->next = sd->completion_queue;		sd->completion_queue = skb;		raise_softirq_irqoff(NET_TX_SOFTIRQ);		local_irq_restore(flags);	}}EXPORT_SYMBOL(dev_kfree_skb_irq);

void dev_kfree_skb_any(struct sk_buff *skb);
— dev_kfree_skb_any() 在中斷或非中斷上下文中都能使用。

void dev_kfree_skb_any(struct sk_buff *skb){	if (in_irq() || irqs_disabled())		dev_kfree_skb_irq(skb);	else		dev_kfree_skb(skb);}EXPORT_SYMBOL(dev_kfree_skb_any);

2.3移動指針
Linux套接字緩衝區中的指針移動操作有:put(放置), push(推), pull(拉)和reserve(保留)等。
2.3.1 put操作
unsigned char *skb_put(struct sk_buff *skb, unsigned int len​​);
將tail 指針下移,增加sk_buff 的len 值,並返回skb->tail 的當前值。
將數據添加在buffer的尾部。

/** *	skb_put - add data to a buffer *	@skb: buffer to use *	@len: amount of data to add * *	This function extends the used data area of the buffer. If this would *	exceed the total buffer size the kernel will panic. A pointer to the *	first byte of the extra data is returned. */unsigned char *skb_put(struct sk_buff *skb, unsigned int len){	unsigned char *tmp = skb_tail_pointer(skb);	SKB_LINEAR_ASSERT(skb);	skb->tail += len;	skb->len  += len;	if (unlikely(skb->tail > skb->end))		skb_over_panic(skb, len, __builtin_return_address(0));	return tmp;}EXPORT_SYMBOL(skb_put);
static inline unsigned char *skb_tail_pointer(const struct sk_buff *skb){	return skb->tail;}

unsigned char *__skb_put(struct sk_buff *skb, unsigned int len​​);
__skb_put() 與skb_put()的區別在於skb_put()會檢測放入緩衝區的數據, 而__skb_put()不會檢查

static inline unsigned char *__skb_put(struct sk_buff *skb, unsigned int len){	unsigned char *tmp = skb_tail_pointer(skb);	SKB_LINEAR_ASSERT(skb);	skb->tail += len;	skb->len  += len;	return tmp;}

2.3.2 push操作:
unsigned char *skb_push(struct sk_buff *skb, unsigned int len​​);
skb_push()會將data指針上移,也就是將數據添加在buffer的起始點,因此也要增加sk_buff的len值。

/** *	skb_push - add data to the start of a buffer *	@skb: buffer to use *	@len: amount of data to add * *	This function extends the used data area of the buffer at the buffer *	start. If this would exceed the total buffer headroom the kernel will *	panic. A pointer to the first byte of the extra data is returned. */unsigned char *skb_push(struct sk_buff *skb, unsigned int len){	skb->data -= len;	skb->len  += len;	if (unlikely(skb->datahead))		skb_under_panic(skb, len, __builtin_return_address(0));	return skb->data;}EXPORT_SYMBOL(skb_push);

unsigned char *__skb_push(struct sk_buff *skb, unsigned int len​​);

static inline unsigned char *__skb_push(struct sk_buff *skb, unsigned int len){	skb->data -= len;	skb->len  += len;	return skb->data;}

__skb_push()和skb_push()的區別與__skb_put() 和skb_put()的區別一樣。
push操作在緩衝區的頭部增加一段可以存儲網絡數據包的空間,而put操作在緩衝區的尾部增加一段可以存儲網絡數據包的空間。

2.3.3 pull操作:
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len​​);
skb_pull()將data指針下移,並減少skb的len值, 這個操作與skb_push()對應。
這個操作主要用於下層協議向上層協議移交數據包,使data指針指向上一層協議頭

/** *	skb_pull - remove data from the start of a buffer *	@skb: buffer to use *	@len: amount of data to remove * *	This function removes data from the start of a buffer, returning *	the memory to the headroom. A pointer to the next data in the buffer *	is returned. Once the data has been pulled future pushes will overwrite *	the old data. */unsigned char *skb_pull(struct sk_buff *skb, unsigned int len){	return skb_pull_inline(skb, len);}EXPORT_SYMBOL(skb_pull);
static inline unsigned char *skb_pull_inline(struct sk_buff *skb, unsigned int len){	return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);}
static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len){	skb->len -= len;	BUG_ON(skb->len < skb->data_len);	return skb->data += len;}

2.3.4 reserve 操作
void skb_reserve(struct sk_buff *skb, unsigned int len​​);
skb_reserve()將data指針和tail 指針同時下移。
這個操作用於在緩衝區頭部預留len長度的空間

/** *	skb_reserve - adjust headroom *	@skb: buffer to alter *	@len: bytes to move * *	Increase the headroom of an empty &sk_buff by reducing the tail *	room. This is only allowed for an empty buffer. */static inline void skb_reserve(struct sk_buff *skb, int len){	skb->data += len;	skb->tail += len;}

3. 例子:
Linux處理一個UDP數據包的接收流程,來說明對sk_buff的操作過程。
這一過程絕大部分工作會在內核完成,驅動中只需要完成涉及數據鏈路層部分。
假設網卡收到一個UDP數據包,Linux處理流程如下:

3.1 網卡收到一個UDP數據包後,驅動程序需要創建一個sk_buff結構體和數據緩衝區,將接收到的數據全部複製到data指向的空間,並將skb->mac_header指向data。
此時有效數據的開始位置data是一個以太網頭部,即鏈路層協議頭。
示例代碼如下:
//分配新的套接字緩衝區和數據緩衝區

工作內容如下圖所示:

3.2 數據鏈路層通過調用skb_pull() 剝掉以太網協議頭,向網絡層IP傳送數據包。
在剝離過程中,data指針會下移一個以太網頭部的長度sizeof(struct ethhdr), 而len 也減去sizeof(struct ethhdr)長度。
此時有效數據的開始位置是一個IP協議頭,skb->network_head指向data,即IP協議頭, 而skb->mac_header 依舊指向以太網頭, 即鏈路層協議頭。
內容如下圖所示:

3.3 網絡層通過skb_pull()剝掉IP協議頭,向UDP傳輸層傳遞數據包。
剝離過程中,data指針會下移一個IP協議頭長度sizeof(struct iphdr), 而len也會減少sizeof(struct iphdr)長度。
此時有效數據開始位置是一個UDP協議頭, skb->transport_header指向data,即UDP協議頭。
而skb->network_header繼續指向IP協議頭, skb->mac_header 繼續指向鏈路層協議頭。
如下圖所示:

3.4 應用程序在調用recv() 接收數據時,從skb->data + sizeof(struct udphdr) 的位置開始復製到應用層緩衝區。
可見,UPD協議頭到最後也沒有被剝離。

Linux的網絡設備驅動架構- 接收封包/傳遞封包

(出處: Linux的網絡設備驅動架構)

從上到下可以劃分為4層,依次為:
—網絡協議接口層:向網絡層提供統一的數據包收發接口。
—設備層:向協議接口層提供統一的用於描述具體網絡設備的屬性和操作的結構體的net_device。
—驅動層:驅動層的各函數是net_device的數據結構的具體成員,使設備硬件完成相應動作的程序。
—物理層,也可以叫媒介層:是完成數據包發送和接收的物理實體。

在設計具體的網絡設備驅動程序時,我們的主要工作是編寫驅動層相關的函數,填充net_device數據結構,並將net_device 註冊進內核。

1. 協議接口層

1.1 dev_queue_xmit()
當上層協議,如ARP或IP協議,需要發送數據包時,將調用dev_queue_xmit()函數發送該數據包,同時傳遞給該函數一個指向struct sk_buff 數據結構的指針,該結構就代表了一個要傳送的數據包dev_queue_xmit()定義如下,注意這是向網絡發送一個數據包。當上層協議,如ARP或IP協議,需要發送數據包時,將調用dev_queue_xmit()函數發送該數據包,同時傳遞給該函數一個指向struct sk_buff 數據結構的指針,該結構就代表了一個要傳送的數據包dev_queue_xmit()定義如下,注意這是向網絡發送一個數據包。

/** *	dev_queue_xmit - transmit a buffer *	@skb: buffer to transmit * *	Queue a buffer for transmission to a network device. The caller must *	have set the device and priority and built the buffer before calling *	this function. The function can be called from an interrupt. * *	A negative errno code is returned on a failure. A success does not *	guarantee the frame will be transmitted as it may be dropped due *	to congestion or traffic shaping. * * ----------------------------------------------------------------------------------- *      I notice this method can also return errors from the queue disciplines, *      including NET_XMIT_DROP, which is a positive value.  So, errors can also *      be positive. * *      Regardless of the return value, the skb is consumed, so it is currently *      difficult to retry a send to this method.  (You can bump the ref count *      before sending to hold a reference for retry if you are careful.) * *      When calling this method, interrupts MUST be enabled.  This is because *      the BH enable code must have IRQs enabled so that it will not deadlock. *          --BLG */int dev_queue_xmit(struct sk_buff *skb){	struct net_device *dev = skb->dev;	struct netdev_queue *txq;	struct Qdisc *q;	int rc = -ENOMEM;	/* Disable soft irqs for various locks below. Also	 * stops preemption for RCU.	 */	rcu_read_lock_bh();	skb_update_prio(skb);	txq = dev_pick_tx(dev, skb);	q = rcu_dereference_bh(txq->qdisc);#ifdef CONFIG_NET_CLS_ACT	skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS);#endif	trace_net_dev_queue(skb);	if (q->enqueue) {		rc = __dev_xmit_skb(skb, q, dev, txq);		goto out;	}	/* The device has no queue. Common case for software devices:	   loopback, all the sorts of tunnels...	   Really, it is unlikely that netif_tx_lock protection is necessary	   here.  (f.e. loopback and IP tunnels are clean ignoring statistics	   counters.)	   However, it is possible, that they rely on protection	   made by us here.	   Check this and shot the lock. It is not prone from deadlocks.	   Either shot noqueue qdisc, it is even simpler 8)	 */	if (dev->flags & IFF_UP) {		int cpu = smp_processor_id(); /* ok because BHs are off */		if (txq->xmit_lock_owner != cpu) {			if (__this_cpu_read(xmit_recursion) > RECURSION_LIMIT)				goto recursion_alert;			HARD_TX_LOCK(dev, txq, cpu);			if (!netif_xmit_stopped(txq)) {				__this_cpu_inc(xmit_recursion);				rc = dev_hard_start_xmit(skb, dev, txq);				__this_cpu_dec(xmit_recursion);				if (dev_xmit_complete(rc)) {					HARD_TX_UNLOCK(dev, txq);					goto out;				}			}			HARD_TX_UNLOCK(dev, txq);			if (net_ratelimit())				pr_crit("Virtual device %s asks to queue packet!\n",					dev->name);		} else {			/* Recursion is detected! It is possible,			 * unfortunately			 */recursion_alert:			if (net_ratelimit())				pr_crit("Dead loop on virtual device %s, fix it urgently!\n",					dev->name);		}	}	rc = -ENETDOWN;	rcu_read_unlock_bh();	kfree_skb(skb);	return rc;out:	rcu_read_unlock_bh();	return rc;}EXPORT_SYMBOL(dev_queue_xmit);

1.2 netif_rx()
同樣,對數據包的接收,是通過向netif_rx() 函數傳遞一個struct sk_buff 數據結構指針來實現:
netif_rx()函數定義如下,注意這是從網絡接收一個數據包到內存。

/** *	netif_rx	-	post buffer to the network code *	@skb: buffer to post * *	This function receives a packet from a device driver and queues it for *	the upper (protocol) levels to process.  It always succeeds. The buffer *	may be dropped during processing for congestion control or by the *	protocol layers. * *	return values: *	NET_RX_SUCCESS	(no congestion) *	NET_RX_DROP     (packet was dropped) * */int netif_rx(struct sk_buff *skb){	int ret;	/* if netpoll wants it, pretend we never saw it */	if (netpoll_rx(skb))		return NET_RX_DROP;	net_timestamp_check(netdev_tstamp_prequeue, skb);	trace_netif_rx(skb);#ifdef CONFIG_RPS	if (static_key_false(&rps_needed)) {		struct rps_dev_flow voidflow, *rflow = &voidflow;		int cpu;		preempt_disable();		rcu_read_lock();		cpu = get_rps_cpu(skb->dev, skb, &rflow);		if (cpu < 0)			cpu = smp_processor_id();		ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);		rcu_read_unlock();		preempt_enable();	} else#endif	{		unsigned int qtail;		ret = enqueue_to_backlog(skb, get_cpu(), &qtail);		put_cpu();	}	return ret;}EXPORT_SYMBOL(netif_rx);

1.3 sk_buff
這裡sk_buff非常重要
sk_buff — 套接字緩衝區,用來在linux網絡子系統中各層之間數據傳遞,起到了“神經中樞”的作用。
當發送數據包時,linux內核的網絡模塊必須建立一個包含要傳輸的數據包的sk_buff,然後將sk_buff傳遞給下一層,各層在sk_buff 中添加不同的協議頭,直到交給網絡設備發送。同樣,當接收數據包時,網絡設備從物理媒介層接收到數據後,他必須將接收到的數據轉換為sk_buff,並傳遞給上層,各層剝去相應的協議頭後直到交給用戶。
sk_buff結構如下圖所示: (define at include/linux/skbuff.h)

2. 設備層

設備層的主要功能是為各種網絡設備定義一個統一的數據結構net_device,實現多種硬件在軟件層次上的統一。
net_device結構體在內核中代表一個網絡設備。
網絡設備驅動程序只需要填充net_device的具體成員,並註冊net_device 即可實現硬件操作函數與內核的掛接。
net_device 是一個巨型結構體,包含了網絡設備的屬性描述和接口操作:
更多細節: net_device & net_device_ops

3. 驅動層:

net_device中的成員(屬性和函數指針),需要被驅動層的具體數值和函數賦值。
對於具體的網絡設備ethx, 軟件工程師需要編寫設備驅動層的函數,這些函数如下:
ethx_open();
ethx_stop();
ethx_tx();
ethx_hard_header();
ethx_get_stats();
ethx_tx_timeout();
ethx_poll();
等。

網絡數據包的接收可以由中斷引發,所以驅動層的另一個主要部分是中斷處理函數。
它負責讀取硬件設備上的數據包並傳給上層協議。
中斷處理函數一般如下:
ethx_interrupt(); — 完成中斷類型判斷等基本工
ethx_rx(); — 完成數據包的生成和傳遞給上層等複雜工作。

對於特定的設備,我們還可以定義其相關的私有數據(private_data) 和操作,並封裝一個私有信息結構體struct ethx_private,讓其指針被賦值給net_device 的priv 成員。
struct ethx_private 結構體中可以包含設備特殊的屬性,自旋鎖/信號量,定時器,及統計信息等,由工程師自己定義。

4. 物理媒介層

媒介層直接對應實際的硬件設備,為了對設備的物理配置和寄存器操作進行普通操作,我們可以定義一組宏或一組訪問函數,對內部寄存器進行訪問。
具體的定義和函數,對特定的硬件緊密相關。下面是設計範例:

 //寄存器定义  #define DATA_REG 0x0004  #define CMD_REG 0x0008    //寄存器读写函数  static u16 xxx_readword(u32 base_addr, int portno)  {  ...  //读取寄存器的值并返回  }    static void xxx_writeword(u32 base_addr, int portno, u16 value)  {  ...   //想寄存器写入数值  }