|
4. 用戶空間和內(nèi)核空間的交互
在解決了在內(nèi)核空間置入可運(yùn)行代碼后,需要解決的是用戶空間和內(nèi)核空間的交互。具體來說,需要達(dá)到以下三個(gè)功能:用戶空間的程序向內(nèi)核空間下的程序控制,用戶空間到內(nèi)核空間的數(shù)據(jù)傳遞,內(nèi)核空間到用戶空間的數(shù)據(jù)傳遞。以下小節(jié),都旨在利用系統(tǒng)提供給我們的各種接口,實(shí)現(xiàn)以上三個(gè)目標(biāo)中的一個(gè)或幾個(gè)。 4.1 printk printk是內(nèi)核用來記錄系統(tǒng)運(yùn)行日志的方法。對(duì)于用戶,可以通過dmesg命令查看近期的系統(tǒng)日志信息,或者直接訪問/var/log/kernel 查看內(nèi)核輸出的所有歷史log。在kernel module中調(diào)用printk是最簡單的傳遞信息到用戶空間的方法。printk函數(shù)的使用方法和用戶態(tài)下的printf類似,區(qū)別是可以通過 KERN_INFO等宏輸出從0-7的指定級(jí)別的log信息。常見的使用方式如下: char myname[] = "chinacodec\n"; printk(KERN_INFO "Hello, world %s!\n", myname); 4.2 偽字符設(shè)備 在linux中,用戶對(duì)設(shè)備的操作往往被抽象為對(duì)文件的操作。利用這一特性,可以通過注冊(cè)和實(shí)現(xiàn)偽字符設(shè)備到內(nèi)核,來實(shí)現(xiàn)用戶進(jìn)程和內(nèi)核空間的交互。當(dāng)在用戶空間執(zhí)行對(duì)該偽設(shè)備的open/read/write/ioctl/mmap/release等操作時(shí),這些被復(fù)用的系統(tǒng)調(diào)用就會(huì)使進(jìn)程從用戶態(tài)進(jìn)入到內(nèi)核態(tài),從而在內(nèi)核中完成事先注冊(cè)的操作,當(dāng)然可以包括對(duì)KAPI的調(diào)用等。 具體方法是,首先,在kernel module中通過register_chrdev注冊(cè)一種偽字符設(shè)備到內(nèi)核,參數(shù)包括:設(shè)備的major號(hào),需要和系統(tǒng)已有設(shè)備不沖突;設(shè)備的名稱name;文件操作函數(shù)集fops。register_chrdev的定義如下: int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); 其中,結(jié)構(gòu)file_operations中包含一系列的函數(shù)指針,對(duì)應(yīng)每種系統(tǒng)調(diào)用(包含read/write/open等),使用中,可以用如下的方式賦值(而不必為每一個(gè)元素都賦值),那些未顯式賦值的元素被賦值為null。 static struct file_operations fops = { .read = device_read, .write = device_write, .ioctl = device_ioctl, .open = device_open, .release = device_release }; register_chrdev一般在kernel module中的init函數(shù)中執(zhí)行,當(dāng)insmod這個(gè)module時(shí),設(shè)備就被注冊(cè)到內(nèi)核中。然后,用戶就可以通過mknod命令可以創(chuàng)建對(duì)應(yīng)的字符設(shè)備文件。然后通過open/write/read/ioctl/mmap/release等系統(tǒng)調(diào)用以訪問設(shè)備文件的方式,訪問該設(shè)備。每種調(diào)用都會(huì)執(zhí)行到在注冊(cè)設(shè)備時(shí)注冊(cè)的對(duì)應(yīng)的文件操作函數(shù)。此外,對(duì)應(yīng)于register_chrdev,從內(nèi)核卸載該偽設(shè)備驅(qū)動(dòng)的函數(shù)為 unregister_chrdev。 以ioctl為例,當(dāng)以上面的file_operations注冊(cè)了偽字符設(shè)備后,當(dāng)用戶對(duì)偽設(shè)備文件執(zhí)行ioctl后,調(diào)用會(huì)進(jìn)入內(nèi)核態(tài),執(zhí)行 device_ioctl函數(shù)。如果我們?cè)谧远x的device_ioctl函數(shù)中去調(diào)用KAPI,就實(shí)現(xiàn)了用戶進(jìn)程對(duì)KAPI的訪問。 4.3 普通文件讀寫 內(nèi)核態(tài)中,可以完成對(duì)用戶文件系統(tǒng)任意文件的訪問。因此,可以在內(nèi)核態(tài)將要輸出的信息寫入文件,寫入后用戶態(tài)程序直接讀取文件就可以完成從內(nèi)核空間向用戶空間的數(shù)據(jù)傳遞。但是在內(nèi)核態(tài)下,對(duì)文件進(jìn)行訪問的調(diào)用函數(shù)和用戶態(tài)下的系統(tǒng)調(diào)用有所區(qū)別。通常的使用方法是通過filp_open打開文件,然后利用獲得的文件指針得到文件操作函數(shù),以讀取和寫入文件,基本代碼如下: tmp _filp = filp_open(dst_file_name, O_RDWR | O_CREAT, 00); tmp _copied = dst_filp->f_op->write(tmp_filep, buffer, size, offset); tmp _len = dst_filp->f_op->read(tmp _filep, buffer, size, offset); 因?yàn)槭窃趦?nèi)核中對(duì)文件操作,所以為了通過系統(tǒng)調(diào)用中對(duì)緩沖區(qū)內(nèi)存地址參數(shù)的檢查,需要修改檢查允許范圍。方法是在read或write操作前通過set_fs擴(kuò)大允許空間,操作后再通過setfs恢復(fù)到此前的允許范圍,具體方法是: orig_fs = get_fs(); set_fs(KERNEL_DS); //write or read set_fs(orig_fs); 4.4 Proc文件系統(tǒng) proc文件系統(tǒng),是當(dāng)前內(nèi)核或內(nèi)核模塊,和用戶交互的主要方式,它通過將虛擬的文件系統(tǒng)掛載在/proc下,利用虛擬文件讀寫在用戶和內(nèi)核態(tài)間傳遞信息。通過內(nèi)核模塊,可以向/proc下注冊(cè)新的文件,指定用戶讀寫該文件時(shí)的回調(diào)函數(shù);這樣,當(dāng)用戶讀寫該文件時(shí),工作在內(nèi)核態(tài)的回調(diào)函數(shù)就可以執(zhí)行信息交互的有關(guān)工作。 向內(nèi)核中注冊(cè)/proc下文件的調(diào)用是create_proc_entry,創(chuàng)建中需要指定文件名,訪問權(quán)限和父節(jié)點(diǎn)名,返回為指向 proc_dir_entry結(jié)構(gòu)的指針。通過該返回指針,可以進(jìn)一步修改文件的用戶id,組id,綁定的內(nèi)核數(shù)據(jù)等;但最為關(guān)鍵的是可以指定用戶讀或?qū)懺撐募r(shí),在內(nèi)核中被執(zhí)行的回調(diào)函數(shù)。下面是一個(gè)向proc文件系統(tǒng)中注冊(cè)新文件的示例: static int __init proc_module_init(void){ entry = create_proc_entry("astring", 0644, myprocroot); if (entry) { entry->data = &string_var; entry->read_proc = &string_read_proc; entry->write_proc = &string_write_proc; } return 0 } static void __exit procfs_exam_exit(void){ remove_proc_entry("astring", myprocroot); remove_proc_entry("myproctest", NULL); } //read proc int string_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data){ count = sprintf(page, "%s", (char *)data); return count; } //write proc int string_write_proc(struct file *file, const char __user *buffer, unsigned long count, void *data){ if (count > STR_MAX_SIZE) { count = 255; } copy_from_user(data, buffer, count); return count; } |
|
|