Linux核心模組自動載入機制 .

來源:互聯網
上載者:User
文章目錄
  • 思考
標題http://www.osplay.org/modules/article/view.article.php?7
  1. 思考
如果想讓核心啟動過程中自動載入某個模組該怎麼做呢?最容易想到的方法就是到/etc/init.d/中添加一個啟動指令碼,然後在/etc/rcN.d/目錄下建立一個符號連結,這個連結的名字以S開頭,這核心啟動時,就會自動運行這個指令碼了,這樣就可以在指令碼中使用modprobe來實現自動載入。但是我們發現,核心中載入了許多硬體裝置的驅動,而搜尋/etc目錄,卻沒有發現任何指令碼負責載入這些硬體裝置驅動程式的模組。那麼這些模組又是如何被載入的呢?

  • 每一個裝置都有Verdon ID, Device ID, SubVendor ID等資訊。而每一個裝置驅動程式,必須說明自己能夠為哪些Verdon ID, Deviece

ID, SubVendor ID的裝置提供服務。以PCI裝置為例,它是通過一個pci_device_id的資料結構來實現這個功能的。例如:RTL8139的pci_device_id定義為:

static struct pci_device_id rtl8139_pci_tbl[] = {
{0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x10ec, 0x8138, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
......
}
MODULE_DEVICE_TABLE (pci, rtl8139_pci_tbl);

上面的資訊說明,凡是Verdon ID為0x10EC, Device ID為0x8139, 0x8138的PCI裝置(SubVendor ID和SubDeviceID為PCI_ANY_ID,表示不限制。),都可以使用這個驅動程式(8139too)。

  • 在模組安裝的時候,depmod會根據模組中的rtl8139_pci_tbl的資訊,產生下面的資訊,儲存到/lib/modules/uname-r/modules.alias檔案中,其內容如下:

alias pci:v000010ECd00008138sv*sd*bc*sc*i* 8139too
alias pci:v000010ECd00008139sv*sd*bc*sc*i* 8139too
......

v後面的000010EC說明其Vendor ID為10EC,d後面的00008138說明Device ID為8139,而sv,和sd為SubVendor ID和SubDevice ID,後面的星號表示任意匹配。

另外在/lib/modules/uname-r/modules.dep檔案中還儲存這模組之間的依賴關係,其內容如下:

(這裡省去了路徑資訊。)
8139too.ko:mii.ko
  • 在核心啟動過程中,匯流排驅動程式會會匯流排協議進行匯流排枚舉(匯流排驅動程式總是整合在核心之中,不能夠按模組方式載入,你可以通過make menuconfig進入Bus

options,這裡面的各種匯流排,你只能夠選擇Y或N,而不能選擇M.),並且為每一個裝置建立一個裝置對象。每一個匯流排對象有一個kset對象,每一個裝置對象嵌入了一個kobject對象,kobject串連在kset對象上,這樣匯流排和匯流排之間,匯流排和裝置裝置之間就組織成一顆樹狀結構。當匯流排驅動程式為掃描到的裝置建立裝置對象時,會初始化kobject對象,並把它串連到裝置樹中,同時會調用kobject_uevent()把這個(添加新裝置的)事件,以及相關資訊(包括裝置的VendorID,DeviceID等資訊。)通過netlink發送到使用者態中。在使用者態的udevd檢測到這個事件,就可以根據這些資訊,開啟/lib/modules/uname-r/modules.alias檔案,根據

alias pci:v000010ECd00008138sv*sd*bc*sc*i* 8139too

得知這個新掃描到的裝置驅動模組為8139too。於是modprobe就知道要載入8139too這個模組了,同時modprobe根據 modules.dep檔案發現,8139too依賴於mii.ko,如果mii.ko沒有載入,modprobe就先載入mii.ko,接著再載入 8139too.ko。

實驗

在你的shell中,運行:

# ps aux | grep udevd

root 25063 ...... /sbin/udevd --daemon

我們得到udevd的進程ID為25063,現在結束這個進程:

# kill -9 25063

然後跟蹤udevd,在shell中運行:

# strace -f /sbin/udevd --daemon

這時,我們看到udevd的輸出如下:

......
close(8) = 0
munmap(0xb7f8c000, 4096) = 0
select(7, [3 4 5 6], NULL, NULL, NULL

我們發現udevd在這裡被阻塞在select()函數中。

select函數原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
第一個參數:nfds表示最大的檔案描述符號,這裡為7(明明是6 ?)。
第二個參數:readfds為讀檔案描述符集合,這裡為3,4,5,6.
第三個參數:writefds為寫檔案描述符集合,這裡為NULL。
第四個參數:exceptfds為異常檔案描述符集合,這裡為NULL。
第五個參數:timeout指定逾時時間,這裡為NULL。

select函數的作用是:如果readfds中的任何一個檔案有資料可讀,或者witefds中的任何一個檔案可以寫入,或者exceptfds中的任何一個檔案出現異常時,就返回。否則阻塞當前進程,直到上訴條件滿足,或者因阻塞時間超過了timeout指定的時間,當前進程被喚醒,select返回。

所以,在這裡udevd等待3,4,5,6這幾個檔案有資料可讀,才會被喚醒。現在,到shell中運行:

# ps aux | grep udevd
root 27615 ...... strace -o /tmp/udevd.debug -f /sbin/udevd --daemon
root 27617 ...... /sbin/udevd --daemon

udevd的進程id為27617,現在我們來看看select等待的幾個檔案:

# cd /proc/27615/fd
# ls -l

udevd的標準輸入,標準輸出,標準錯誤全部為/dev/null.
0 -> /dev/null
1 -> /dev/null
2 -> /dev/null

udevd在下面這幾個檔案上等待。
3 -> /inotify
4 -> socket:[331468]
5 -> socket:[331469]
6 -> pipe:[331470]
7 -> pipe:[331470]

由於不方便在運行中插入一塊8139的網卡,因此現在我們以一個隨身碟來做實驗,當你插入一個隨身碟後,你將會看到strace的輸出,從它的輸出可以看到 udevd在select返回後,調用了modprobe載入驅動模組,並調用了sys_mknod,在dev目錄下建立了相應的節點。

execve("/sbin/modprobe", ["/sbin/modprobe", "-Q", "usb:v05ACp1301d0100dc00dsc00dp00"...]
......
mknod("/dev/sdb", S_IFBLK|0660, makedev(8, 16)) = 0
......

這裡modprobe的參數"usb:v05AC..."對應modules.alias中的某個模組。

可以通過udevmonitor來查看核心通過netlink發送給udevd的訊息,在shell中運行:

# udevmonitor --env

然後再插入隨身碟,就會看到相關的發送給udevd的訊息。

== 核心處理過程 ==:

  • 這裡我們以PCI匯流排為例,來看看在這個過程中,核心是如何處理的。當PCI匯流排驅動程式掃描到一個新的裝置時,會建立一個裝置對象,然後調用 pci_bus_add_device()函數,這個函數最終會調用kobject_uevent()通過netlink向使用者態的udevd發送訊息。

int pci_bus_add_device(struct pci_dev *dev)
{
int retval;
retval = device_add(&dev->dev);

......

return 0;
}

device_add()代碼如下:

int device_add(struct device *dev)
{
struct device *parent = NULL;

dev = get_device(dev);

......

error = bus_add_device(dev);
if (error)
goto BusError;
kobject_uevent(&dev->kobj, KOBJ_ADD);
......
}

device_add()在準備好相關資料結構後,會調用kobject_uevent(),把這個訊息發送到使用者空間的udevd。

int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[])
{
struct kobj_uevent_env *env;
const char *action_string = kobject_actions[action];
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
struct kset_uevent_ops *uevent_ops;
u64 seq;
int i = 0;
int retval = 0;

......

/* default keys */
retval = add_uevent_var(env, "ACTION=%s", action_string);
if (retval)
goto exit;
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;

/* keys passed in from the caller */
if (envp_ext) {
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, envp_ext[i]);
if (retval)
goto exit;
}
}

......

/* 通過netlink發送訊息,這樣使用者態的udevd進程就會從select()函數返回,並做相應的處理。 */
#if defined(CONFIG_NET)
/* send netlink message */
if (uevent_sock) {
struct sk_buff *skb;
size_t len;

/* allocate message with the maximum possible size */
len = strlen(action_string) + strlen(devpath) + 2;
skb = alloc_skb(len + env->buflen, GFP_KERNEL);
if (skb) {
char *scratch;

/* add header */
scratch = skb_put(skb, len);
sprintf(scratch, "%s@%s", action_string, devpath);

/* copy keys to our continuous event payload buffer */
for (i = 0; i < env->envp_idx; i++) {
len = strlen(env->envp[i]) + 1;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp[i]);
}

NETLINK_CB(skb).dst_group = 1;
netlink_broadcast(uevent_sock, skb, 0, 1, GFP_KERNEL);
}
}
#endif

......
return retval;
}

思考

現在我們知道/dev目錄下的裝置檔案是由 udevd負責建立的,但是在核心啟動過程中,需要mount一個根目錄,通常我們的根目錄是在硬碟上,比如:/dev/sda1,但是硬碟對應的驅動程式沒有載入前,/dev/sda1是不存在的, 如果沒有/dev/sda1,就不能通過mount /dev/sda1 /來掛載根目錄。另一方面udevd是一個可執行檔,如果連硬碟驅動程式到沒有載入,根目錄都不存在,udevd就不能運行。如果udevd不能運行,那麼就不會自動載入磁碟驅動程式,也就不能自動建立/dev/sda1。這不是死結了嗎?那麼你的Linux是怎麼啟動的呢?

參考資料: Essential Linux Device Drivers

相關文章

聯繫我們

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

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

A Free Trial That Lets You Build Big!

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

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

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

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