2013/03/04

(中文試譯) Kernel Space 與 User Space 間的溝通介面介紹


(中文試譯) Kernel Space 與 User Space 間的溝通介面介紹

原文: Kernel Space - User Space Interfaces (version0.8, July 2008)
原作者: Ariane Keller mailto:ariane.zerospam.keller@tik.ee.ethz.ch
 

(圖片出處:http://www.myextralife.com/comic/comic-interface/)


* 版本會一直修改,目前為 v0.1
* 紅色字體為小弟我才疏學淺,不知其中文翻譯應該如何表達
* 綠色字體為我自己的筆記與想法
* 灰色字體TBD (To Be Done) 這部分我保留原文,因為目前我還沒有實際操作過,翻譯起來也無意義。
* 如果對翻譯內容有所疑問與指教,歡迎留言或Mail。
* 如果IE顯示亂碼,請改變編碼: 檢視-> 編碼 -> Unicode(UTF-8)
 
 
murmurings:
之前我閱讀小鄭學長的Blog"軟體學徒forever"。看見他的一個奇妙單元叫"中文試譯"。
我感覺這單元不僅可以提升自己對文章與知識的理解,更可以增加英文能力。感覺真是一舉數得。
很多部份我還沒實作過也沒認真研讀過,我就先放TBD了。
期待未來有天可以補上。

本文開始======================================================================
這篇文章談論到許多有趣的方法關於Kernel(2.6)與使用者空間(User Space)之間的溝通介面。
我們將會解釋到Socket、Procfs、系統呼叫、一般的檔案與記憶體映射處理。請見以下目錄:

1. 簡介 
  • 1.1 前言
  • 1.2 如何使用本文件與讀者群
  • 1.3 使用本文件的需求
  • 1.4 獲得程式碼
  • 1.5 取得更多資訊
  • 1.6 免除責任聲明(Disclaimer)
  • 1.7 版權(Copyright)

2. Procfs, Sysfs, and Similar Kernel Interfaces
  • 2.1 簡介
  • 2.2 Procfs
  • 2.3 Sysfs (TBD)
  • 2.4 Configfs (TBD)
  • 2.5 Debugfs (TBD)
  • 2.6 Sysctl (TBD)
  • 2.7 字元裝置(Character Devices)

3. Socket Based Mechanisms
  • 3.1 簡介
  • 3.2 UDP Sockets (UPD服務端口)
  • 3.3 Netlink Sockets

4. Ioctl
  • 4.1 簡介
  • 4.2 實作
  • 4.3 其他資源與延伸閱讀

5. Kernel System Calls
  • 5.1 簡介
  • 5.2 實作
  • 5.3 其他資源與延伸閱讀

6. Sending Signals from the Kernel to the User Space
  • 6.1 簡介
  • 6.2 實作
  • 6.3 其他資源與延伸閱讀

7. Upcall
  • 7.1 簡介
  • 7.2 實作
  • 7.3 其他資源與延伸閱讀

8. Mmap
  • 8.1 簡介
  • 8.2 實作
  • 8.3 其他資源與延伸閱讀


1. 簡介   

1.1 前言

本文章提供了全部已經存在的的溝通方式之簡介。
目的是使開發者能直接了解如何在Kernel與User Space之間傳輸資料與溝通。
每個通訊機制在各自的章節中都有詳細的描述,
各個章節都區分成:
  • Description(描述/簡介): 我們描述基本的操作方式
  • Implementation(實作): 我們提供具有簡單描述的程式碼作為範例
  • Resources(資源) & Further Reading(延伸閱讀): 我們列出有用的連結與參考書目
所有的程式碼都在Linux kernel 2.6.23測試過了。
當然還是有可能在其他版本(較早的或最近)上造成失敗的情況。但是我會持續的更新。
如果你找到了一些Bug(臭蟲)或是某些本文章沒提到的方式,請Email給作者。
請注意: 
當你使用這些核心模組(Kernel Module)時,可能導致系統當機或是系統重新開機!
意味著在新增核心模組前,請您先關閉所有開啟的應用程式或是預先儲存檔案以防萬一。
你可以考慮執行些程式於虛擬機器(virtual machine)中,這樣即使當機也不會損害你原始的系統。

 

1.2 如何使用本文件與讀者群

這份文件是寫給有系統設計經驗的人看的。
我們著重在對每個各別的機制先有個簡單的描述,之後提供範例來輔助之。
我們假設讀者看得懂本文件所提供的程式碼並且可以套用這些範例於讀者自己的模組裡。
讀者可以先看一下簡介說明,然後研讀一下程式碼,最後在比較程式碼與解說。
範例儘量的保持簡單易讀,但是現實生活中的模組可能較為複雜。

1.3 使用本文件的需求

系統核心必須包括所需的Linux工具,這邊的範例都在2.6.23上測試通過了。
  • 最高管理者權限(root)
  • 必須懂C語言
  • 有作業系統的基本知識

1.4 獲得程式碼

本文章重頭到尾都有範例的程式碼,這些程式碼可以以tar.gz的型態下載。

1.5 取得更多資訊

每個章節都包含延伸閱讀的部分,但我本人比較喜歡以下資源:
  • 一些O'Reilly出版的傑出書籍
    • Linux Device Driver, 3rd edition, Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman, February 2005
    • Understanding the Linux Kernel, 3rd edition, By Daniel P. Bovet, Marco Cesati, November 2005
    • Understanding the Linux Network Internals, 1rd edition, By Christian Benvenuti, December 2005
  • Linux Kernel程式碼的資料夾"Documentation"內
  • Linux程式碼本身。有個不錯的網站(http://lxr.linux.no/linux/Documentation/)可以瀏覽程式碼。他可以對關鍵字(如,程式名、定義、變數等)做搜尋。搜尋到以後,甚至有超連結可以連結到跟其有關的檔案或程式碼。

1.6 免除責任聲明(Disclaimer)

使用本文章的資訊請自己承擔風險。對於本文的內容,我沒有任何潛在的責任.
使用這邊的觀念、範例或是其他包函的內容都請自行承擔風險。
載入這邊的kernel module可能會導致你的電腦當機(i.e.毫無反應),然後你必須重新開機。
因此,請記得先儲存好你重要的資料在你載入kernel module之前。
All copyrights are owned by their owners, unless specifically noted otherwise.
(版權屬於其擁有者,除非特別聲明而另當別論。)
Use of a term in this document should not be regarded as affecting the validity of any trademark or service mark.
(使用本文件的任何術語,不應該被視為影響任何商標或服務標誌的有效性。)
Naming of particular products or brands should not be seen as endorsements.
(命名不應該被看作是特定的產品或品牌代言。)
James Note,
這邊說到版權的法律文句,我還真是不太懂意思。
強烈建議您備份你的系統並且週期性的備份。

1.7 版權(Copyright)

Copyright © 2008 Ariane Keller.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.



2. Procfs, Sysfs, and Similar Kernel Interfaces 

2.1 簡介

這些檔案系統為選擇性安裝的,可能在你的系統上沒有。
可以查看"/lib/modules/`uname -r`/build/.config",此會告訴你,你的kernel有建構哪些檔案系統。
James Note,
"/lib/modules/`uname -r`/build/.config"無法使用。check please!
為了在核心空間(kernel-space)與使用者空間(user-space)交換資訊,
Linux核心提供幾個RAM-base的檔案系統。
交換的介面其實就是這些檔案系統,基於檔案的格式。
通常一個檔案都用一個值(i.e. fd(file descriptors))來代替,但是也可能是一組值。
使用者空間的應用程式可以藉由標準函式"read(2)"和"write(2)"來存取這些值。
而大多數的kernel模組的讀寫函式都對應到callback function,用這些callback function來存取這些值。
James Note,
a couple of 是翻譯成"兩個"或是"幾個"? 這邊應該翻譯為"幾個"比較好。

儘管RAM-base的檔案系統提供了相同的功能(就是想要做個interface),但是他們卻是為不同的目的設計的。
然而,也可以把這些檔案系統做其他的用途。
"應該用甚麼樣的檔案系統?"或是"為什麼需要不同的檔案系統?"
這些問題常在Linux Kernel的討論串上面看見,
這些爭論常是無止盡的,而且每個開發者都有自己的獨特見解。
對比於使用socket需要標準的read和write函式,
在使用者空間早已經提供許多有用的工具可以來直接把資料送給kernel space。
舉例來說,如cat(1) 和 echo (1)。這些方法已是眾所皆知並且可以直接書寫於scripts裡面。

2.2 Procfs

描述
Procfs (Process file system)是在這個類別最被人廣泛知道的介面,位於/proc裡。
原本的設計是要提供每個程序(proces)的資訊,像是目前的狀態、使用者空間裡開起的所有fd等。
儘管這才是他原本的目的,但proc現在用於其他更多的用途,如下:
  • 提供一些執行中的系統資訊,像是CPU的資訊、中斷的資訊、可以使用的記憶體或是核心的版本等。
  • "ide devices", "scsi devices" 和 "tty's" 的資訊.
  • 網路的資訊,像是ARP表、網路狀態或是服務端口(socket)的使用表列等。
這裡有個特殊的子目錄: /proc/sys。
允許對執行中的程式直接下參數設定。通常每個檔案裡面都只存在一個值,這值可能代表: 
  • 極限值(舉例來說,暫存器的最大值)
  • 開啟或關閉一些功能(如例行性任務)
  • 代表核心變數
所有/proc/sys底下的的目錄或是文件並非用/proc的讀取方式來運作,反而是使用另一個機制"sysctl"。
請參閱section sysctl章節。
注意,雖然/proc被廣泛的使用,但是他應該只用於提取出與process有關的資料,其他目的比較不建議使用。

實作
為了要使用procfs,需要在編譯的時候主動加入到Linux kernel程式碼。
這必須要設定參數"CONFIG_PROC_FS=y",而大多數的基本設定都把預設值設為開啟了。
Procfs支援兩種不同的API給kernel模組:
  • 傳統的API(The legacy procfs API): 這是十分容易使用開發的API,只要處理的資料夠小的話。"夠小"是小於一個page的大小,舉例來說,i386的系統每page是4096 位元組。
  • Seq_file API: Seq_file是為方便處理讀取要求而設計的。其支援大於PAGE_SIZE的讀取要求,並且她提供去讀取一個list的機制,可以收集一個list的所有元素,並且傳輸這些元素們給使用者空間。
James Note,
這篇文章有說到範例: http://daydreamer.idv.tw/rewrite.php/read-50.html


James Note,
Legacy通常翻譯成舊的,網路上舉例來說有些舊設備不支援某些新功能,我們做新產品還是要容忍這些舊的設備,我們就會叫他Legacy Device。但這邊的說法,我不認為這是個不常用或說過時的機制,我看很多人還是這麼用(我自己也是常用)。
所以我翻成"傳統"的。


1. 傳統 procfs API
procfs.c 傳統 procfs API
傳統的procfs API允許創造一個檔案或是目錄。每個檔案你必須註冊兩個callback函式: 
一個是給使用者要這個檔案的時候用的,另一個是入的時候用的。
放在Linux Kernel程式碼資料夾裡面的"Linux Kernel Procfs Guide"對這個API有很棒的描述。
所以在此我們只給與簡單的範例:
這個核心模組會產生一個目錄還有一個檔案。如果你的資料超過個"PAGE_SIZE"位元組,很可能會造成錯誤。
這是因為讀取動作所使用的這個API:
read(char *page, char **start, off_t off, int count, int *eof, void *data)
第一個參數是一個暫存器其大小對應到一個page。
如此一來,如果你的檔案大於一個page,讀這個動作就要切分成很多次才可。

James Note,
注意用這個傳統的procfs,其一次讀取的大小不要超過一個page大小。


2. Seq_file API
"seq_file"這個API只對讀取要求有用,對寫的動作無影響。
這個API隱藏了"PAGE_SIZE"的範圍限制,並且她提供了一個API來汲取一整串列的物件資料,
收集完所有資料後,把他們放進一個檔案當中。一個核心模組的範例可以參考http://lwn.net/Articles/22359/.

其他資源與延伸閱讀
 

2.3 Sysfs (TBD)

Description
Sysfs was designed to represent the whole device model as seen from the Linux kernel. It contains information about devices, drivers and buses and their interconnections. In order to represent the hierarchy and the interconnections sysfs is heavily structured and contains a lot of links between the individual directories. As for kernel 2.6.23 it contains the following 9 top-level directories:

  • sys/block/ all known block devices such as hda/ ram/ sda/
  • sys/bus/ all registered buses. Each directory below bus/ holds by default two subdirectories:
    • device/ for all devices attached to that bus
    • driver/ for all drivers assigned with that bus.
  • sys/class/ for each device type there is a subdirectory: for example /printer or /sound
  • sys/device/ all devices known by the kernel, organised by the bus they are connected to
  • sys/firmware/ files in this directory handle the firmware of some hardware devices
  • sys/fs/ files to control a file system, currently used by FUSE, a user space file system implementation
  • sys/kernel/ holds directories (mount points) for other filesystems such as debugfs, securityfs.
  • sys/module/ each kernel module loaded is represented with a directory.
  • sys/power/ files to handle the power state of some hardware
Implementation
In order to use sysfs it needs to be compiled with the Linux kernel source code. This is done by setting the parameter CONFIG_SYSFS=y.
The philosophy behind sysfs is to represent each value with a dedicated file. In addition each file has a maximum size of PAGE_SIZE bytes.
For a kernel module there are three possibilities to use a file below /sys:
  1. module parameter
  2. register new subsystem
  3. debugfs: debugfs, mounted in /sys/kernel/debug. More information about debugfs.
Module Parameter API
Similar to command line arguments for applications, Linux kernel modules may allow a set of parameters. These parameters can not only be specified upon module insertion but also during module run time. A module parameter can be defined with the following macro:
module_param_named(name, value, type, perm)
This macro creates a parameter called "name" which corresponds to the variable with name "value" of type "type". There are many predefined types such as byte (for a single character), int (for an integer) or charp (for a string). It is also possible to add new types. The file include/linux/stat.h provides all predefined types as well as an introduction how to define new types.
The module_param macro creates a file called /sys/modules/module_name/name with the access rights specified by perm. Depending on the specified access rights, the file - and thereby the parameter value - can be read or written. If perm is set to 0 the file is not created, and therefore the parameter cannot be accessed during run time.
The module does not receive a notification when a user reads or writes a given parameter, but the value is silently changed. Therefore it is not possible to do some additional stuff when a parameter changes its value. This may be acceptable in some circumstances as for changing a debug level, but in most circumstances the module wants to do some additional stuff such as sanity checks or manipulating a data structure.

Standard Sysfs API
The standard sysfs API uses a dedicated terminology: A file is called an attribute, the function executed upon reading an attribute is called show and the one for writing an attribute store.
Before starting with the implementation of a module which uses sysfs you have to figure out which subdirectory it belongs to. If you deal with a bus, it belongs to bus/, with a file system it belongs to fs/ or with a block device it belongs to block/. The API to use depends on the given subdirectory. We first show an example which uses the low level sysfs functions to add a new directory to fs/, and in a second example we show how to add a new entry to the bus/ directory.

1. new fs/ entry
sysfs_ex.c creates the directory /sys/fs/myfs/ along with two files first and second. Both containing one single integer value.
The first step is to declare our subsystem. This can be done with the use of the decl_subsys macro (on top of the file). This macro creates a struct kset with the name myfs_subsys.
The module_init() function performs the proper registration of our subsystem: The macro kobj_set_kset_s initializes myfs_subsys so that it will be part of the fs_subsys. The field myfs_subsys.kobj.ktype points to a structure which holds all the attributes as well as the functions to read and write the attributes. And finally a call to register_subsystem() registers our subsystem.
Files are generally represented by a struct attribute. This struct holds the name as well as the access permission for the corresponding file, but no data. Therefore you have to create your own attribute type which consists of at least the struct attribute and the value corresponding to that file.
By design all attributes share the same show and store functions. Each time one of these two functions is invoked it gets the corresponding struct attribute as an argument. Therefore in the show and store functions you can obtain the value corresponding to the file being read/written and you can manipulate it accordingly. For this purpose you need the macro container_of(ptr, type, member). ptr is a pointer to the member of the struct. type is the type of the struct this member is emeded in and member is the name of the member within the struct.

2. new bus/ entry
sysfs_ex2.c use of sysfs in combination with a bus. It provides the possibility to read and write one value with the help of "my_pseude_bus".
First of all we define our bus my_pseudo_bus. Then we create our attribute with the help of the BUS_ATTR macro. In the init function we register our pseudo bus and we create a file (attribute). If we would like more than one attribute we would have to use BUS_ATTR several times and provide for each attribute its own store and show function. This example is similar to the debugging facility of the scsi bus, which is implemented in drivers/scsi/scsi_debug.c
Resources and Further Reading
  1. Understanding the Linux Kernel p. 527
  2. Linux Device Driver p. 362, 377 for the bus directory
  3. Linux kernel source code: drivers/scsi/scsi_debug.c
 

2.4 Configfs (TBD)

Description
The configfs is somewhat the counterpart of the sysfs. It can be seen as a filesystem based manager of kernel objects. An important difference between configfs and sysfs is that in configfs all objects are created from user space with a call to mkdir(2). The kernel responds with creating the attributes (files) and then they can be read and written by the user. If the user no longer needs the files, he calls rmdir(2) and everything gets deleted. Therefore the life cycle of a configfs object is fully controlled by user space.
Each time mkdir is invoked a new "config_item" is created by the kernel implementation. This config_item represents the files (attributes), the show and store callback functions as well as the associated value. Therefore each mkdir creates a new directory along with new files which represent new values.
Configfs has the same limitations than sysfs: each file should represent only one value and it should be smaller than PAGE_SIZE bytes.
Implementation
In order to use configfs it needs to be compiled with the Linux kernel source code. This is done by setting the parameter CONFIG_CONFIGFS_FS=y.
In order to access configfs it has to be mounted with the following command:
mount -t configfs none /config
The Linux kernel documentation provides a good manual for configfs along with an example module. Therefore we do not describe the configfs implementation aspects.
Resources and Further Reading
  • Linux kernel source code: Documentation/filesystems/configfs
 

2.5 Debugfs (TBD)

Description
Debugfs is a simple to use RAM based file system especially designed for debugging purposes. Developers are encouraged to use debugfs instead of procfs in order to obtain some debugging information from their kernel code. Debugfs is quite flexible: it provides the possibility to set or get a single value with the help of just one line of code but the developer is also allowed to write its own read/write functions, and he can use the seq_file interface described in the procfs section.
Implementation
In order to use debugfs it needs to be compiled with the Linux kernel source code. This is done by setting the parameter CONFIG_DEBUG_FS=y.
Before having access to the debugfs it has to be mounted with the following command.
mount -t debugfs none /sys/kernel/debug

debugfs.c kernel module that implements the "one line" API for a variable of type u8 as well as the API with which you can specify your own read and write functions.

All the "one line" APIs start with debugfs_create_ and are listed in include/linux/debugfs.h
The API with which you can provide your own read and write functions is similar to the one of procfs. In contrast to sysfs, you may create directories and files without having to care about a given hierarchy.
Resources and Further Reading
 

2.6 Sysctl (TBD)

Description
The sysctl infrastructure is designed to configure kernel parameters at run time. The sysctl interface is heavily used by the Linux networking subsystem. It can be used to configure some core kernel parameters; represented as files in /proc/sys/*. The values can be accessed by using cat(1), echo(1) or the sysctl(8) commands. If a value is set by the echo command it only persists as long as the kernel is running, but gets lost as soon as the machine is rebooted. In order to change the values permanently they have to be written to the file /etc/sysctl.conf. Upon restarting the machine all values specified in this file are written to the corresponding files in /proc/sys/.
Implementation
sysctl.c sysctl example module: write an integer to /proc/sys/net/test/value1 and value2 respectively
Each entry in the /proc/sys directory is represented by an entry in a table maintained by the Linux kernel, arranged in a hierarchy. A directory is represented by an entry pointing to a subtable. A file is represented by an entry of type struct ctl_table. This entry consists of the data represented by this file along with some access rules.
New files and directories can be added by expanding one of the subtables. In this example we add a new directory called test below the /proc/sys/net/ directory. Our directory has got two files: value1 and value2. Each of these files hold an integer variable which can have a value between 10 and 20. The user root is allowed to change the entries whereas normal user are allowed to read the entries.
Each file is represented with an entry in the test_table[] array:

static ctl_table test_table[] = {
{
.ctl_name = CTL_UNNUMBERED,
.procname = "value1",
.data = &value1,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec_minmax,
.strategy = &sysctl_intvec,
.extra1 = &min,
.extra2 = &max
},
...
}

The struct ctl_table entries are:

  • .ctl_name: For new entries this has to be CTL_UNNUMBERED (according to Documentation/sysctl/ctl_unnumbered.txt).
  • .procname: The name of the file.
  • .data: A reference to the data we want to be shown in the file.
  • maxlen: The size of the data.
  • mode: Access permissions (read, write, execute for user, group, others)
  • proc_handler: The routine which handles read and write requests. There is a set of default routines declared near the end of include/linux/sysctl.h
  • strategy: Some routine that enforces additional access control. In this example it checks that the value to be written is between min and max.
static ctl_table test_net_table[] = {
{
.ctl_name = CTL_UNNUMBERED,
.procname = "test",
.mode = 0555,
.child= test_table
},
{ .ctl_name = 0 }
};

This table represents our test directory. The entry .child says that the elements below this directory are represented by the table test_table, discussed above.

static ctl_table test_root_table[] = {
{
.ctl_name = CTL_UNNUMBERED,
.procname = "net",
.mode = 0555,
.child= test_net_table
},
{ .ctl_name = 0 }
};

This table represents the directory to which we want to attach our new directory. In this example, this is the net directory. In the module_init() function we have to register this root table with a call to
register_sysctl_table(test_root_table);
 
Resources and Further Reading
  • Linux kernel source code: net/core/sysctl_net_core.c
  • Linux kernel Documentation: Documentation/sysctl/ctl_unnumbered.txt

2.7 字元裝置(Character Devices)描述

顧名思義,這個介面就是設計給字元裝置的驅動程式(character device drivers),
並且通常都是拿來作kernel space與user space的溝通。
(舉例來說,如果使用者有足夠的權限,他可以直接借由"echo "hi there" > /dev/tty1"對虛擬終端機作寫的動作)
James Note:
Devcie 可分成兩種類型:Character Device 與Block Device,
前者每次會傳輸固定的資料量,儲存裝置多為此類,而後者的資料量則不需固定。
在ls 的結果中,最左邊的第一個字元b,就是代表這個Device 為Block Device,若為Character Device,最左邊的第一個字元會是c。



所有的核心模組都可以把自己註冊成字元裝置(character device),並且提供一些讀寫的函式來處理資料。
代表字元裝置的檔案都放置在"/dev"目錄之下。
(這目錄下你也可以找到區塊裝置(block devices),但我們這邊不討論它)。通常這邊的檔案都對應到硬體裝置。
實作
cdev.c 核心模組會把由kernel選到的主要號碼(majorNumber)填寫到系統記錄中。minorNumber可以為0。
就像是其他基於檔案系統的方法,這核心模組也要指定讀寫的callback函式。因此我們必須透過以下函式來註冊:
register_chrdev(unsigned int major, const char *name, struct file_operations *ops);
  • major:這是這個device的主要號碼,如果填0就是讓kernel自動選定。
  • name:是這個裝置的名稱,未來會在""目錄下面看見。
  • ops:是個指向讀寫函式的指標
跟目前所看見的其他基於檔案系統的方法,使用者可以創建一個裝置檔案(device file)透過以下的呼叫:
mknod /dev/arbitrary_name c majorNumber minorNumber
James Note:
1. 簡單說就是在console下面只要打上以上的命令,"/dev"目錄下就會出現/dev/arbitrary_name其主要代號是"majorNumber"
2. 他寫到system log的方式是透過printk。

其他資源與延伸閱讀
  • man mknod(1)
  • Linux Device Driver chapter 3.


 

3. Socket Based Mechanisms

3.1 簡介

上個章節我們介紹檔案系統的通訊方法,這邊我們要介紹基於Socket(服務端口)的介面。
Socket 可以由 kernel-space 送一些通知給使用者空間。
對比於檔案系統,檔案系統是由Kernel產生一個檔案,
但是User-Space的使用者並不知道這檔案有任何的改變,user必須去讀取才知道變化。
但是 socket 通訊機制可以在 user-space 的應用程式中產生一個 listen 的 port,
如果 kernel 於任何時間傳輸資訊,user-space 的應用程式都可以馬上得知。
這樣的通訊機制使user-space 與 kernel-space是對等同步的。
 
這裡有些socket位址家族代號,可以完成上述的目標:
AF_INET
基本上這個式設計用於網路上的溝通,但是UDP的socket卻也可以用於Kernel module與user-space的溝通。
但是使用UDP在單點上的溝通將會多出許多的負擔。
AF_PACKET
允許使用者定義所有封包的標頭(header)
AF_NETLINK (netlink sockets)
這是一個特別的設計用於kernel-space 與 user-space間的溝通。
目前有許多種netlink socket的型態於kernel中,其有不同對應的功用。

這裏我們稍微帶到UDP的socket,因為他是最有彈性的通訊方法。
接著我們會介紹到netlink,並且專注於"一般型的netlink sockets(generic netlink sockets)"。

3.2 UDP Sockets (UPD服務端口)

描述
我們簡單的介紹一下UDP socket,因為他在user-space可以算是一個well-know的裝置並且提供許多彈性功能。
舉例來說,使用UDP socket更可以達到某機器的kernel module與另一台機器user-space的應用程式間的通訊。
James Note:
為什麼說是well-know,因為UDP的socket算是一個裝置。
如果他有service name,更可以藉由讀/etc/service得之服務名與服務端口號碼的對應。

實作
udpRecvCallback.c kernel module 提供一個callback function 用來接收由port 5555得到的資料
udpUser.c user space 的程式會送出資料給kernel,並接收kernel回傳的資料。
User-space的程式送一個""的字串給聽在port 5555的一個Kernel Module。這個kernel收到訊息後會
(1)印出接收到的字串到系統的歷史記錄檔中(system log) 和
(2)把接收到的資料再送回user-space的應用程式。
user-space的應用程式最後會印出這個訊息到標準輸出。
James Note:
注意,kernel module呼叫kprintf()就會把資料寫到system log中
user-space的程式碼無須解釋,因為很多的文件就有在說明socket的企劃。而kernel module的實作就必須了解一些linex 核心的知識。
在kernel-module中初始化的"module_init"中會執行以下三個行為: 
  1. 產生一個socket來接收UDP的封包。我們產生一個socket(可能只是一個fd號碼)並綁在(bind())這個user- space已知的端口(也就是5555號端口),往後我們就可以用他來收取user-space所傳來的封包。除此之外,我們指定了一個callback function來做為每次接收到資訊後的處理函式。這個callback函式是個"interrupt context"。這意味著這個處理函式只能執行某些限定的功能,因此她是無法幫忙送出資料的。
  1. (承上)因為這個callback function的處理函示無法執行送出的功能,我們定義了一個專屬的work queue。本work queue會延緩送出資訊直到我們離開interrupt context。
  1. 最後,我們產生一個socket來送出資訊給user-space的應用程式。

James Note, 
這個callback function就是cb_data()這個函式,她幫我們把這次的事件包成一個work並丟到這個work queue後就離開interrupt context。 此後當輪到本work執行後,work內註冊的函式是"send_answer()"。


每次kernel module收到一個封包,callback function "cb_data()"就會被執行。
這個callback只會做一件事情,就是提交"send_answer()"放在work queue裡。
當kernel決定了沒有更重要的事情要做之後,他就會來執行"send_answer()"。
"send_answer()"會藉由"skb_dequeue"來接收還放在queue(i.e. sockets-receive-queue)裡面的資料。
因為兩次執行"send_answer()"之間可能不僅僅只有一個收到的資訊(因為排程是kernel說了算),
必須要藉由"skb_dequeue"來作分析。
如果想要知道有幾個資料還放在queue中,可以呼叫"skb_queue_len()"來取得個數。
一但取得了queue中的資料,kernel module就會記錄在系統歷史記錄中(system log),
並且呼叫"sock_sendmsg()"把資料送回給user-space的程式。
Since this function assumes to be executed from user space, 
we have to adjust the boundaries for the allowed memory region with the help of the set_fs macro.
(因為我們假設這支原本發起的程式是由user-space所喚起的,我們必須用巨集"set_fs"來調整boundaries。)


3.3 Netlink Sockets

描述
Netlink是個特殊的IPC(InterProcess Communition),專用於傳遞資訊於kernel-space 與 user-space 之間,
並提供一個全多功的通道於Linux kernel 與 user-space之間。
在user-space 的程序,netline使用標準socket APIs ; 但是在kernel-space卻是用到特殊的socket API。
對比於用於TCP/IP網路的位址代號AF_INET,netlink則是使用AF_NETLINK。
netlink相對於其他的溝通方式有以下的優點:
  • 因為socket可以馬上使用,所以只需簡單的在標準linux kernel上面加入一點固定的原始碼就好。而且沒有任何汙染kernel或是造成驅動程式不穩定的風險。
  • netlink socket是asynchronous(非同步的),因為他們有自己的佇列(queue)。義味著使用netlink並不像使用system call一樣會干擾到kernel的棑程。(因為system call必須馬上停止原本的排成來執行system call的內容。?!)
  • netlink可以支援multicast的功能(群播)。
  • 真正的雙向溝通,發起人可以是kernel或是user space的應用程式。
  • 比較於標準UDP socket,負擔較小(因為標頭與處理過程都較為簡單)。
但是也有以下的缺點:
  • 每個用netlink的程式都必須去核心的"include/linux/netlink.h"註冊自己的協定號碼。而註冊後必須重新編譯才有效。
  • 最大有32組協定號碼,如果太多人用很容易就用完。
為避免這些缺點,通常會使用"Generic Netlink Family"
/*
以下沒有翻譯是因為我們常用的netlink AP似乎比這邊的範例簡單。感覺這邊的範例和我們常用的似乎有點不同。因為沒深究這邊的所已先到此為止。
常用的如:nlmsg_unicast(), netlink_kernel_create(),好用也直觀,這邊的範例似乎都沒有看見。
未來我想在補上一篇對netlink的實作。
*/
/*
Generic Netlink Family 好像有特殊的API與Family ID,可以參閱:
http://www.linuxfoundation.org/collaborate/workgroups/networking/genericnetlinkhowto
*/

(TBD)
It acts as a Netlink multiplexer, in a sense that different applications may use the generic netlink address family.
Generic Netlink communications are essentially a series of different communication channels which are multiplexed on a single Netlink family. Communication channels are uniquely identified by channel numbers which are dynamically allocated by the Generic Netlink controller. Kernel or user space users which provide services, establish new communication channels by registering their services with the Generic Netlink controller. Users of the service, then query the controller to see if the service exists and to determine the correct channel number.
Each generic netlink family can provide different "attributes" and "commands". Each command has its own callback function in the kernel module and may receive messages with different attributes. Both commands and attributes, are "addressed" by an identifier.
Implementation

gnKernel.c kernel module that implements a generic netlink family (CONTROL_EXMPL)
gnUser.c user space program that sends a message to the kernel and receives an answer back

Here we describe how to use this generic netlink protocol to exchange data between user space and kernel space.
User Space:
Although the user space application could be written just with the help of the well known socket operations it is not reasonable to do so. For convenient user space programming there exists the libnl netlink library. It provides functions dedicated to be used for generic netlink socket communication. The libnetlink library supports generic netlink as of version 1.1, so probably you need to download the actual version from http://people.suug.ch/~tgr/libnl/. A program that uses this library needs to be compiled with -lnl specified.
User Space Sending Phase::

  1. nl_handle_alloc: create a socket
  2. genl_connect: connect to the NETLINK_GENERIC socket family.
  3. genl_ctrl_resolve: resolve the ID for the particular generic netlink family we want to talk with. In this example we have called the family CONTROL_EXMPL.
  4. genlmsg_put: create the generic netlink message header. In most cases you can leave all the arguments as in the example except the DOC_EXMPL_C_ECHO argument. This specifies which callback function of your kernel module gets executed. In this example there is just one callback function.
  5. nla_put_*: put the data into the message. All the possibilities are listed in the file attr.c of libnl. The second argument is used by the kernel module to distinguish which attribute was sent. In this example we have only one attribute: DOC_EXMPL_A_MSG which is a null terminated string.
  6. nl_send_auto_complete: send the message to the kernel

Receiving Phase:
  1. nl_socket_modify_cb: Add a callback function to the socket. This callback function gets executed when the socket receives a message. In the callback function the message needs to be decoded. We use nla_parse for this. Using genlmsg_parse would be more specific, but I could not link genlmsg_parse with my program. The nla_get_functions are the counterpart of the nla_put_functions, and are used to get a specific attribute from the message.
  2. nl_recvmsgs_default: wait until a message is received.

Kernel Module

In the module_init function we need to register our generic netlink family with a call to genl_register_family. As an argument we have to specify a struct genl_family which holds the name of our family (CONTROL_EXMPL). In a second step we have to register the functions that get executed upon receiving a message from the user space. This is done with genl_register_ops which takes as an argument the family to which this function belongs and a struct genl_ops. This struct specifies the actual callback function as well as a security policy checked before the actual callback function gets executed. This means that if you want to receive, for example an integer, but the user space program sends a string, your callback function does not get invoked.
The actual callback function is doc_exmpl_echo. It performs two things: prints the received message, and sends a message back to the user space process. The callback function has as an argument a struct genl_info *info which holds the already parsed message. This struct contains an array which has an entry for each possible attribute. Our example has only one attribute (DOC_EXMPL_A_MSG). The data related to this attribute is saved in info->attrs[DOC_EXMPL_A_MSG];. In order to obtain the data for a given attribute there is a simple function: nla_data.
The sending process is very similar to the user space sending process.
  1. genlmsg_new: this allocates a new skb that will be sent to the user space. Since we do not yet know the final size, we use the macro NLMSG_GOODSIZE.
  2. genlmsg_put: fills the generic netlink header. All messages sent by kernel have pid = 0
  3. nla_put_string: write a string into the message.
  4. genlmsg_end: finalize the message
  5. genlmsg_unicast: send the message back to the user space program.

其他資源與延伸閱讀

 

4. Ioctl

4.1 簡介

除了我們在第2節(Procfs, Sysfs, and Similar Kernel Interfaces)所提到的read和write的函式外,
只要是檔案(James Note: 其實Linux所有東西都看做是一個檔案),
ioctl提供了另外一種讀寫的控制命令可供使用。
ioctl這個機制可以視為一個單一個系統呼叫(system call),其中包含了各種對應到核心函式的命令。
一個ioctl有三個引數,分別是: (1)檔案(或是socket)的fd、(2)對應到某個操作命令的值(命令代碼)、(3)資料。
這個API怎麼知道要去控制誰與做甚麼命令,靠的就是(a)fd 和 (b)對應到某個操作命令的值。
概念上你可以自己定義你自己想要的值來對應到不同的ioctl命令,
但是這是非常不建議的,還是用大家都熟知的系統設定值(system wide)好。
這樣確保ioctl不會去控制到錯誤的裝置而導致非預期行為。
獲取獨一的命令代碼的詳盡方式記錄在Linux的kernel文件"Documentation/ioctl-number.txt"中。
ioctl提供不同的引數型態如下:
  • 無須任何資料的命令
  • 寫資料到何新的命令
  • 由核心讀取資料的命令
  • 核心模組讀取資料並用新資料取代
當命令代碼被產生後(如同"Documentation/ioctl-number.txt"所描述的方法),這些引數的型態就定義好了。

4.2 實作

ioctl.c 核心模組利用ioctl結合了一個character device(註一)。其大小為最大200位元組。
ioctl_user.c 用戶端的應用會傳輸資料給核心透過ioctl

James Note: 
Devcie 可分成兩種類型:Character Device 與Block Device,前者每次會傳輸固定的資料量,儲存裝置多為此類,而後者的資料量則不需固定。 在ls 的結果中,最左邊的第一個字元b,就是代表這個Device 為Block Device,若為Character Device,最左邊的第一個字元會是c。

為了要驗證ioctl的方式,我們延伸了在" Character Device"章節所說到的字元裝置(character device)範例。
除了可以使用讀(read)和寫(write)這些系統呼叫來傳遞資料,現在還可以用ioctl(註)。
我們設置了兩個ioctl命令,一個給送資料給核心,一個給由核心讀取資料。
延伸的部分很直接,就是定義你的ioctl handler在"struct file_operations"之中。
(其實裡面本來就已經定義好基本的read和write了。)
這個ioctl的callback中會有一個抉擇器來姐義不同的命令代碼。
如果輸入ioctl的命令代碼是未定義的,則回傳"ENOTTY",意思是"不正確的ioctl"。

(註)James Note: 
這句話是說原本字元裝置就已經有掛read和write的函式了,但是現在多了一個ioctl。
ioctl裡面會有兩個命令代碼,0給READ;1給WRITE。

static struct file_operations fops = {

.read = device_read,
.write = device_write,
.ioctl = device_ioctl,
};

4.3 其他資源與延伸閱讀



5. Kernel System Calls

5.1 簡介


系統呼叫(System calls)使用於當使用者空間的程式需要使用到一些核心資料或是一些由核心提供的服務。
在目前的Linux核心版本(2.6.23)存在有324個系統呼叫。
所有的系統呼叫對於許多的應用程式提供一個有用的函式(舉例說: 檔案的處理、網路的處理與程序(process)相關的處理)。
如果一個特殊的系統呼叫只給一種特殊的程式來使用,這是非常沒有道理的。
通常系統呼叫都被一些glib的wrapper函式包起來(舉例來說,open()、socket()、getpid())。
系統內部而言,每個系統呼叫都對應到一個特殊的號碼。
當使用者空間的程序呼叫了一個系統呼叫,那CPU就會切換到核心模式(kernel mode)並且執行對應的核心函式。
為了要實際的由使用者磨式切換到核心模式,這裡會牽涉到一些組譯的指令(assembly instructions)。
對於x86的架構而言,這裡有兩種可能的做法:(1) "int $0x80"或是(2)較新的指令"sysenter",
兩者都可能會觸發:
  • CPU轉換到核心模式
  • 必要的暫存器必須被儲存起來
  • 有效輸入的檢查
  • 載入由使用者提供號碼的系統呼叫

如果系統呼叫的服務已經結束,那函式"system_call"就會檢查還有沒有其他的工作要做
(如重新排程(rescheduling)或式處理訊號(signal processing))。
最後恢復到使用者模式的上下文(process context)。

5.2 實作

系統呼叫是底層的架構,如此一來他們與Linux核心息息相關。為了要新增一個系統呼叫到Linux核心之中,
你必須先修改一些Linux核心檔案:
1. include/asm-i386/unistd.h
這個檔案定義了讓使用者空間可以辨認的系統呼叫之號碼,把您要加入的系統呼叫放置在最後面,並且增加系統呼叫的總數。舉例如下:
#define __NR_mysyscall325
#define NR_syscalls 326
2. include/linux/syscalls.h
這個檔案包含了所有系統呼叫的宣告,把您想加入的放在最後面。
asmlinkage long sys_mysyscall(char __user *data, int len);

3. arch/i386/kernel/syscall_table.S
這個檔案是個表格,記錄了所有可以使用的系統呼叫。為了讓您的系統呼叫可以使用,把你的新增物加到本檔案的最底部:
.long sys_mycall

4. 新增一個全新的目錄 mysyscall
因為系統呼叫必須整合進核心,我們在Linux程式碼的最外層目錄(top level directory)裡,新增一個"mysyscall"目錄給我們的系統呼叫
5. Makefile
Add the new directory to the variable core-y (search for core-y.*+=) in the top level Makefile. 
/*我沒做過,不確定實際要如何增加本"directory"*/
6. 把你的系統呼叫的程式寫在已下檔案內 mysyscall/mysyscall.c
這個範例是說,把使用者空間提供的資料印到系統歷史檔案(system log)內,並且把"hello world"吐回給使用者空間的程式。
#include 
#include 
asmlinkage long sys_mysyscall(char __user *buf, int len)
{
char msg [200];
if(strlen_user(buf) > 200)
return -EINVAL;
copy_from_user(msg, buf, strlen_user(buf));
printk("mysyscall: %s\n", msg);
copy_to_user(buf, "hello world", strlen("hello world")+1);
return strlen("hello world")+1;
}

7. 在你的目錄底下新增一個Makefile,並加入以下命令: 
obj-y:= mysyscall.o

到此為止你已經對新增自己私人的系統呼叫成功完成核心建構的部分。
再重新編譯與安裝好心的核心之後,使用者空間的應用程式就可以使用新的系統呼叫。
以下就是個使用自己新增的系統呼叫的範例:
#include 
#include 
#include 
#include 
#define MYSYSCALL 325
int main(){
char *buf [20];
memcpy(buf, "hi kernel", strlen("hi kernel") +1);
syscall(MYSYSCALL, buf, 10);
printf("kernel said %s\n", buf);
return 0;
}
 

5.3 其他資源與延伸閱讀



 

6. Sending Signals from the Kernel to the User Space

6.1 簡介

這個方法與其他的做法有些許不同,因為只有何心可以送訊號(Signal)給使用者空間,但相反則不行。
除此之外,如果想帶大量資料(the amount of data)也是不可以的。
使用者空間看signal有兩種型態,(1)一種是"normal"訊號,他不帶有任何資料;(2)"realtime"訊號,他可以挾帶32位元的資料。
兩者最主要的不同,是"realtime"訊號是有佇列的,"normal"訊號則沒有。
這意謂著在一個程序(process)還沒有辦法處理訊號的時候,如果送來的多個"normal"訊號,那當本程序可以處理訊號的時候,
只會收到一個。反觀"realtime"訊號,送出多少個就可以收到多少個。
使用者空間的程序(process)註冊一個訊號處理函式(signal handler function)於kernel中。
這個舉動等同於把這個訊號處理函式的位址寫在這個程序的描述(process descriptor)裡面。
每當這個程序收到對應的訊號,這個訊號處理函式就會被處發起動。
傳送訊號的階段包含兩個部分:
  1. 更新新的訊號於程序描述中。
  2. 如果process被重新選取來執行(be rescheduled)或是他剛由一個中段處理後回來,他會先檢查是不是有未處理的訊號。如果有,他會先執行第一個訊號處理函式函後才繼續做剩下的程式部分。
 

6.2 實作

signal_kernel.c 核心模組送一個siganl給使用者空間的程序。核心需要知道此程序的程序ID(PID)。因此使用者空間的程序把自己的PID寫入debugfs的"signalconfpid"檔案裡。
signal_user.c 使用者空間的程式負責接收訊號
為了要送一個signal由核心空間到使用者空間,核心必須先知道使用者空間程序的PID。
如此一來這個模型範例會把使用者空間的程式之PID先送給核心。
當核心一收到這個PID,他會找到對應的process descriptor,並且把訊號送給她。
所有和signal相關的資訊都會記錄於struct siginfo之中。我們得以用si_int送32位元的資料。
為了讓使用者空間的程式認知這個訊號是real-time的訊號以便我們可以存取資料的部分,我們必須把si_code 設定成SI_QUEUE。
否則的話我們使用者空間的函式就不會收到資料。
James Note: 可以帶資料的訊號叫real-time signal
James Note:
In order that + 句子 ....
約同 In order to + V ....

6.3 其他資料與延伸閱讀

  • man 7 signal
  • Understanding the Linux Kernel chapter 11

 

7. Upcall(向上呼叫)

7.1 簡介

Linux Kernel中向上呼叫(Upcall)的功能可以使一個Kernel Module(核心模組)於user space中開啟一個功能。
舉例來說,可以開啟一個帶有參數或是環境變數的執行程式於User Space中。

7.2 實作

usermodehelper.c 核心模組來開啟User Space的一個程式或程序
callee.c 藉由Kernel開啟的User Space程式,會吃Kernel帶上來的參數或環境變數
在我們的範例中,核心模組會呼叫"usermodehelper()"這個函式來帶起於User Space中的程式"callee"。
因為"callee"並非由shell所帶起來的,我們無法使用printf來證明本程式是否真的被正確的執行。
所以我們用"beep"(電腦嗶嗶聲)來驗證其正確性,並且我們讓核心模組來決定要重覆"嗶"幾聲。
"callee"是藉由以下的API於Kernel Space中所呼叫起來的:
int call_usermodehelper(char *path, char **argv, char **envp, enum umh_wait wait)
其引數的描述如下:
  • path: User Space中程式的路徑
  • argv: User Space中程式的引數
  • envp: 環境變數的集合
  • umh_wait: 一個列舉(Enum)來聲明核心模組須要等待多久才能繼續往下執行:
    • UMH_NO_WAIT: 無須等代
    • UMH_WAIT_EXEC: wait for the exec, but not the process
    • UMH_WAIT_PROC: 等到User Space的程序結束


James Note:
這是一個簡單的溝通方法-向上呼叫(注意是由kernel->User-Space)。
簡單說你在一個ko module或是ISR buttom中呼叫"call_usermodehelper",
他就自動去路徑執行你放好的執行檔。

簡單舉例,
call_usermodehelper("/usr/app",NULL,NULL,UMH_NO_WAIT);
系統自動帶起"/usr/app"這支程式。
我想他的缺點就是,這是只能叫起一個執行檔,無法送一個訊號給已經運行的process。

 

7.3 其他資源與延伸閱讀

  • Understanding the Linux Network Internals Chapter 5

 

8. Mmap

8.1 簡介

記憶體映射(Memory mapping)是唯一個在kernel-spacec與user-space之間傳送資料,
無須複製內容的方法。
也是一個最快的方法來處理大量資料的傳輸。
傳統的read(2)/write(2)函式和MMap有個主要的區分點。
當資料透過MMap傳輸的時候,完全沒有控制訊息。這意味著user-space可以放資料進入記憶體中,
但是核心卻完全不曉得資料已經放進來了。
反之亦同,核心把資料放入共享記憶體中,user-space也完全不曉得發生此事。
這特色說明了使用MMap必須伴隨著其他通訊方式來傳輸控制訊息,
或者共享的記憶體必須週期性的來輪詢來發現新的事件發生。
和傳統的read(2)/write(2)函式相同的,MMap可以伴隨不同的檔案系統和socket來使用。
James Note,
作者說的這個區分點很怪,profs也透過read()和write(),也是非同步啊!!
其實大概只有socket可以透過receive()相關函式來等待資料的變化。


 8.2 Implementation (TBD)

mmap_simple_kernel.c kernel module that provides the mmap system call based on debugfs.
mmap_user.c user space program that will share a memory area with the kernel module

Of course we need some memory that we want to map between user space and kernel space. In this example we share some RAM but if you are writing a device driver, this could be the memory of your device. We use debugfs and attach the memory area to a file. This allows the user space process to access the shared memory area with the help of a file descriptor.
The user space program uses the system calls open, mmap, memcpy and close, which are all documented in the Linux man pages.
The kernel module is more challenging. Please note that the discussed module offers only the most basic functionality, and that usually mmap is just one of the functions provided to handle a file. In the module_init function we create the file as discussed in section debugfs. Since it is an example module, our file_operations struct contains only three entries: my_open, my_close and my_mmap.
my_open
In this function we allocate the memory that will later be shared with the user space process. Since memory mapping is done on a PAGE_SIZE basis we allocate one page of memory with get_zeroed_page(GFP_KERNEL) We initialize the memory with a message form the kernel that states the name of this file. We set the private_data pointer of this file to the allocated memory in order to access it later in the my_mmap and my_close function
my_close
This function frees the memory allocated during my_open.
my_mmap
This function initializes the vm_area_struct to point to the mmap functions specific to our implementation. mmap_open und mmap_close are used only for bookkeeping. mmap_nopages is called when the user space process references a memory area that is not in its memory. Therefore mmap_nopages does the real mapping between user space and kernel space. The most important function is virt_to_page which takes the memory area to be shared as an argument and returns a struct page that can be used by the user space to access this memory area.
 

8.3 其他資源與延伸閱讀

  • man(2) mmap
  • Linux Device Driver, Chapter 15


後記:
感覺即使看懂要翻譯也是要一番工夫,
舉例說明好了,
"Each mechanism is described in its own section"
這句話我一開直接翻譯成:
"每個作法都有詳細的描述於各自的小節中,"
後來我把他寫成:
"每個通訊機制在各自的章節中都有詳細的描述"
感覺較為通順。
更何況對於這些機制我也不是完全實作過,有時候還真的不好翻譯呢!!(笑)
看來還要多多磨練。

後來比較不耐煩,我就沒有在訂正翻譯了。真不好意思~~~



 


2 則留言:

  1. 俊彥 希望你能維持研究所做學問的態度繼續下去

    回覆刪除
  2. 謝謝鼓勵!請問是李教授嗎?! ^^

    回覆刪除