深入淺出 insmod, #1

jollen 發表於 November 15, 2006 8:52 PM

作者/陳俊宏
http://www.jollen.org

這篇文章的目的是為了解釋 Linux 驅動程式課程中,經常被詢問的部份,我把這個地方以紅色來標示:

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};

是的,就是 *owner 這個 field 常被問起。*owner 的用途倒底是什麼?(以下以 kernel 2.4.x 為主做說明)

insmod 基本觀念

我用流程的方式來說明。不過在這之前,先請第 128 號系統呼叫 sys_init_module 出場。sys_init_module system call 的主要用途是載入 kernel module,以下是他的原型宣告

long sys_init_module(const char *name_user, struct module *mod_user);

在這裡我們看到第 2 個參數 *mod_user 就是此行的重點 struct module。另外,sys_init_module 系統呼叫透過 glibc 的 init_module() system call 來叫用。以下是 INIT_MODULE(2):

NAME
       init_module - initialize a loadable module entry

SYNOPSIS
       #include <linux/module.h>

       int init_module(const char *name, struct module *image);

DESCRIPTION
       init_module loads the relocated module image into kernel space and runs
       the module's init function.

       The module image begins with a module structure and is followed by code
       and data as appropriate.  The module structure is defined as follows:

另外,INIT_MODULE(2) 提到「 This system call is only open to the superuser.」,因此 insmod 只能以 root 身份執行。insmod 的原始碼包含在 modutils 套件裡,insmod 實作呼叫 sys_init_module system call 的程式位於 util/sys_nim.c,函數名稱為 sys_init_module,程式碼內容簡單易懂,節錄如下:

#ifndef CONFIG_USE_SYSCALL

extern int init_module(const char *name, const struct module *info);

int
sys_init_module(const char *name, const struct module *info)
{
  return init_module(name, info);
}

#else

#define __NR_sys_init_module  __NR_init_module
_syscall2(int, sys_init_module, const char *, name,
          const struct module *, info)

#endif

請了解這裡的 sys_init_module 是一個 modutils 自行實作的函數,用來提供給 insmod 指令呼叫,與 kernel 的 sys_init_module system call 是二個不同的東西。insmod 指令的原始碼位於 modutils 套件裡的 insmod/insmod.c,我們已經了解到,insmod.c 會呼叫 util/sys_nim.c:sys_init_module() 來載入 kernel module。

insmod 的程式碼中,載入 kernel module 的工作主要是由 init_module() 來執行,我們透過 init_module() 的原始碼來說明幾件事件:

static int init_module(const char *m_name, struct obj_file *f,
		       unsigned long m_size, const char *blob_name,
		       unsigned int noload, unsigned int flag_load_map)
{
	struct module *module;
	struct obj_section *sec;
	void *image;
	int ret = 0;
	tgt_long m_addr;

	sec = obj_find_section(f, ".this");
	module = (struct module *) sec->contents;
	m_addr = sec->header.sh_addr;

	module->size_of_struct = sizeof(*module);
	module->size = m_size;
	module->flags = flag_autoclean ? NEW_MOD_AUTOCLEAN : 0;

	sec = obj_find_section(f, "__ksymtab");
	if (sec && sec->header.sh_size) {
		module->syms = sec->header.sh_addr;
		module->nsyms = sec->header.sh_size / (2 * tgt_sizeof_char_p);
	}
	if (n_ext_modules_used) {
		sec = obj_find_section(f, ".kmodtab");
		module->deps = sec->header.sh_addr;
		module->ndeps = n_ext_modules_used;
	}
	module->init = obj_symbol_final_value(f, obj_find_symbol(f, "init_module"));
	module->cleanup = obj_symbol_final_value(f,
		obj_find_symbol(f, "cleanup_module"));

	sec = obj_find_section(f, "__ex_table");
	if (sec) {
		module->ex_table_start = sec->header.sh_addr;
		module->ex_table_end = sec->header.sh_addr + sec->header.sh_size;
	}
	sec = obj_find_section(f, ".text.init");
	if (sec) {
		module->runsize = sec->header.sh_addr - m_addr;
	}
	sec = obj_find_section(f, ".data.init");
	if (sec) {
		if (!module->runsize ||
		    module->runsize > sec->header.sh_addr - m_addr)
			module->runsize = sec->header.sh_addr - m_addr;
	}
	sec = obj_find_section(f, ARCHDATA_SEC_NAME);
	if (sec && sec->header.sh_size) {
		module->archdata_start = sec->header.sh_addr;
		module->archdata_end = module->archdata_start + sec->header.sh_size;
	}
	sec = obj_find_section(f, KALLSYMS_SEC_NAME);
	if (sec && sec->header.sh_size) {
		module->kallsyms_start = sec->header.sh_addr;
		module->kallsyms_end = module->kallsyms_start + sec->header.sh_size;
	}
	if (!arch_init_module(f, module))
		return 0;

	/*
	 * Whew!  All of the initialization is complete.
	 * Collect the final module image and give it to the kernel.
	 */
	image = xmalloc(m_size);
	obj_create_image(f, image);

	if (flag_load_map)
		print_load_map(f);

	if (blob_name) {
		int fd, l;
		fd = open(blob_name, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
		if (fd < 0) {
			error("open %s failed %m", blob_name);
			ret = -1;
		}
		else {
			if ((l = write(fd, image, m_size)) != m_size) {
				error("write %s failed %m", blob_name);
				ret = -1;
			}
			close(fd);
		}
	}

	if (ret == 0 && !noload) {
		fflush(stdout);		/* Flush any debugging output */
		ret = sys_init_module(m_name, (struct module *) image);
		if (ret) {
			error("init_module: %m");
			lprintf("Hint: insmod errors can be caused by incorrect module parameters, "
				"including invalid IO or IRQ parameters.\n"
			        "      You may find more information in syslog or the output from dmesg");
		}
	}

	free(image);

	return ret == 0;
}

重要的觀念如下:

1. struct module 是「module descriptor」,kernel 的 module manager 以 module descriptor 來維護 kernel module。我們把 struct module 稱為 module object

2. 看藍色的部份,運算子的左邊。init_module() 負責「setup module object」。

3. 看紅色的部份,module object 的內容主要是讀取自 kernel module object file 裡的特定 section。

4. 最後是綠色的部份,讀完 object file 裡的特定 section,並 setup 好 module object 後,就呼叫 sys_init_moddule() 來載入 module object 至 kernel-space。

了解主要的觀念與流程後,再來就是重要的小結了!

insmod 整體觀念

insmod 指令使用 sys_init_module 系統服務,將  kernel module 載入到 kernel 的 address space。例如,載入 hello.o 的命令為:

# insmod hello.o

要了解 kernel module 的載入過程,必須由 insmod 的原始碼開始討論。 這裡只是一開始,所以沒有對程式碼的細節做太多介紹,當然這是後面才需要做的功課。

關於 *owner 的小結

透過以上的說明,我們就可以來回答「*owner 的用途倒底是什麼?」的問題了。

*owner 的 data type 是 struct module,即 module object 的資料結構。在整個 kernel module 的載入過程中,module object 是由 insmod 指令所設置,並且是代表 kernel module object file。

所以,*owner 是 kernel module 的觀念,與「Linux 驅動程式」沒有關係。fops 才是Linux 驅動程式的觀念。

最後,為什麼 fops 裡要有 *owner 呢?答案就很明顯了:Linux 驅動以 fops->owner 來表示「我這個驅動程式是屬於(is owned)那一個 kernel module」

關於更多 *owner,我們可以想到的是,驅動程式的 usage count 是紀錄在 module object 裡,而不是 file operation(思考為什麼),所以當我們對 usage count 做 increment/decrement 時,就需要 module object。

另外,關於寫 Linux 驅動程式時,「要不要管 *owner」的答案也是很清楚了,基本上,我們可以不用理會這個 field,但是如果要給 *owner 欄位值的話,根據 Linux kernel 的說明,我們只要這麼寫即可:

static struct file_operations xxx_ops = {
	owner:		THIS_MODULE,
	...
};

THIS_MODULE 的定義是(include/linux/module.h):

#define THIS_MODULE (&__this_module)

__this_module 是一個外部符號,用來表示「這個驅動程式所屬的 module object」。如果是分層的驅動程式,那麼這個 field 就要填。

課堂上我們再以 busmouse.c / logibusmouse.c 來說明這個部份。

Also See

 

Jollen's Blog 使用 Github issues 與讀者交流討論。請點擊上方的文章專屬 issue,或 open a new issue

您可透過電子郵件 jollen@jollen.org,或是 Linkedin 與我連絡。更歡迎使用微信,請搜尋 WeChat ID:jollentw