linux入口函數
⑴ 在linux內核中,注冊字元設備驅動程序的函數是
字元設備驅動程序框架 1、寫出open、write函數 2、告訴內核 1)、定義一個struct file_operations結構並填充好 static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_mole變數 */ .open = first_drv_open, .write = first_drv_write, }; 2)、把struct file_operations結構體告訴內核 major = register_chrdev(0, "first_drv", &first_drv_fops); // 注冊, 告訴內核相關參數:第一個,設備號,0自動分配主設備號,否則為主設備號0-255 第二個:設備名第二個:struct file_operations結構體 4)、register_chrdev由誰調用(入口函數調用) static int first_drv_init(void) 5)、入口函數須使用內核宏來修飾 mole_init(first_drv_init); mole_init會定義一個結構體,這個結構體裡面有一個函數指針指向first_drv_init這個函數,當我們載入或安裝一個驅動時,內核會自動找到這個結構體,然後調用裡面的函數指針,這個函數指針指向first_drv_init這個函數,first_drv_init這個函數就是把struct file_operations結構體告訴內核 6)、有入口函數就有出口函數 mole_exit(first_drv_exit); 最後加上協議 MODULE_LICENSE("GPL"); 3、mdev根據系統信息自動創建設備節點: 每次寫驅動都要手動創建設備文件過於麻煩,使用設備管理文件系統則方便很多。在2.6的內核以前一直使用的是devfs,但是它存在許多缺陷。它創建了大量的設備文件,其實這些設備更本不存在。而且設備與設備文件的映射具有不確定性,比如U盤即可能對應sda,又可能對應sdb。沒有足夠的主/輔設備號。2.6之後的內核引入了sysfs文件系統,它掛載在/sys上,配合udev使用,可以很好的完成devfs的功能,並彌補了那些缺點。(這里說一下,當今內核已經使用netlink了)。 udev是用戶空間的一個應用程序,在嵌入式中用的是mdev,mdev在busybox中。mdev是udev的精簡版。首先在busybox中添加支持mdev的選項: Linux System Utilities ---> [*] mdev [*] Support /etc/mdev.conf [*] Support subdirs/symlinks [*] Support regular expressions substitutions when renaming device [*] Support command execution at device addition/removal 然後修改/etc/init.d/rcS: echo /sbin/mdev > /proc/sys/kernel/hotplug /sbin/mdev -s 執行mdev -s :以『-s』為參數調用位於 /sbin目錄寫的mdev(其實是個鏈接,作用是傳遞參數給/bin目錄下的busybox程序並調用它),mdev掃描 /sys/class 和 /sys/block 中所有的類設備目錄,如果在目錄中含有名為「dev」的文件,且文件中包含的是設備號,則mdev就利用這些信息為這個設備在/dev 下創建設備節點文件。一般只在啟動時才執行一次 「mdev -s」。熱插拔事件:由於啟動時運行了命 令:echo /sbin/mdev > /proc/sys/kernel/hotplug ,那麼當有熱插拔事件產生時,內核就會調用位於 /sbin目錄的mdev。這時mdev通過環境變數中的 ACTION 和 DEVPATH,來確定此次熱插拔事件的動作以及影響了/sys中的那個目錄。接著會看看這個目錄中是否「dev」的屬性文件,如果有就利用這些信息為 這個設備在/dev 下創建設備節點文件重新打包文件系統,這樣/sys目錄,/dev目錄就有東西了下面是create_class的原型: #define class_create(owner, name) / ({ / static struct lock_class_key __key; / __class_create(owner, name, &__key); / }) extern struct class * __must_check __class_create(struct mole *owner, const char *name, struct lock_class_key *key); class_destroy的原型如下: extern void class_destroy(struct class *cls); device_create的原型如下: extern struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) __attribute__((format(printf, 5, 6))); device_destroy的原型如下: extern void device_destroy(struct class *cls, dev_t devt); 具體使用如下,可參考後面的實例: static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev; firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */ class_device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); 下面再來看一下應用程序如何找到這個結構體的在應用程序中我們使用open打開一個設備:如:open(/dev/xxx, O_RDWR); xxx有一個屬性,如字元設備為c,後面為讀寫許可權,還有主設備名、次設備名,我們注冊時 通過register_chrdev(0, "first_drv", &first_drv_fops)(有主設備號,設備名,struct file_operations結構體)將first_drv_fops結構體注冊到內核數組chrdev中去的,結構體中有open,write函數,那麼應用程序如何找到它的,事實上是根據打開的這個文件的屬性中的設備類型及主設備號在內核數組chrdev裡面找到我們注冊的first_drv_fops,實例代碼: #include #include #include #include #include #include #include #include #include #include static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev; volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL; static int first_drv_open(struct inode *inode, struct file *file) { //printk("first_drv_open\n"); /* 配置GPF4,5,6為輸出 */ *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2))); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; //printk("first_drv_write\n"); _from_user(&val, buf, count); // _to_user(); if (val == 1) { // 點燈 *gpfdat &= ~((1<<4) | (1<<5) | (1<<6)); } else { // 滅燈 *gpfdat |= (1<<4) | (1<<5) | (1<<6); } return 0; } static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_mole變數 */ .open = first_drv_open, .write = first_drv_write, }; int major; static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); // 注冊, 告訴內核 firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */ gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); gpfdat = gpfcon + 1; return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 卸載 class_device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); iounmap(gpfcon); } mole_init(first_drv_init); mole_exit(first_drv_exit); MODULE_LICENSE("GPL"); 編譯用Makefile文件 KERN_DIR = /work/system/linux-2.6.22.6 all: make -C $(KERN_DIR) M=`pwd` moles clean: make -C $(KERN_DIR) M=`pwd` moles clean rm -rf moles.order obj-m += first_drv.o 測試程序: #include #include #include #include /* firstdrvtest on * firstdrvtest off */ int main(int argc, char **argv) { int fd; int val = 1; fd = open("/dev/xyz", O_RDWR); if (fd < 0) { printf("can't open!\n"); } if (argc != 2) { printf("Usage :\n"); printf("%s \n", argv[0]); return 0; } if (strcmp(argv[1], "on") == 0) { val = 1; } else { val = 0; } write(fd, &val, 4); return 0; }
⑵ 問個嵌入式linux中驅動編程的問題,有個模塊 static int _init embed_hello_init (void)
1)embed_hello_init 不是結構體名,是函數名
2)int _init部分,int 表示函數的返回值類型,是整型
扣除_init去看,static int embed_hello_init (void),就是定義一個靜態的無入參函數,返回值是整型。這些概念跟嵌入式,linux,驅動都沒有任何關系,是C語法的概念。
回到_init,這是linux 內核編程的一個特殊宏,,展開是一個gcc的擴展屬性語法,限制了函數鏈接時放elf文件的那個section。
定義大概如下(不同內核版本可能有差異):
#define __init __attribute__((__section__(".init")))
通過把init函數限制在一個固定的section,一個作用是在啟動時簡單遍歷section調用初始化函數即可,另外一個作用是在初始化完成後,可以馬上釋放該section所佔空間給系統用(因為初始化函數通常只在系統啟動後執行一次)。
⑶ linux 能不能看到線程的入口函數
你好。 在分時系統里應該沒什必要吧 setpriority/getpriority,這兩個函數描述的是改變進程優先順序。 但是在linux中線程就是一個輕量級的進程, 所以這兩個函數是可以作用於單獨的線程的 如果我的回答沒能幫助您,請繼續追問。
⑷ linux驅動的入口函數是不是只用mole_init()
這個是模塊的入口,就這一個!Linux要的就是統一介面,你還希望它復雜一點嗎?就這一個多好!
⑸ 在linux編程中若一個用戶程序希望將一組數據傳遞給kernel有幾種方式
教科書里的Linux代碼例子都已作古,所以看到的代碼不能當真,領會意思就行了
比如以前的init進程的啟動代碼
execve(init_filename,argv_init,envp_init);
現在改為
static void run_init_process(char *init_filename)
{
argv_init[0] = init_filename;
kernel_execve(init_filename, argv_init, envp_init);
}
好的,聰明人就發現,linux內核中調用用戶空間的程序可以使用init這樣的方式,調用 kernel_execve
不過內核還是提供了更好的輔助介面call_usermodehelper,自然最後也是調用kernel_execve
調用特定的內核函數(系統調用)是 GNU/Linux 中軟體開發的原本就有的組成部分。但如果方向反過來呢,內核空間調用用戶空間?確實有一些有這種特性的應用程序需要每天使用。例如,當內核找到一個設備, 這時需要載入某個模塊,進程如何處理?動態模塊載入在內核通過 usermode-helper 進程進行。
讓我們從探索 usermode-helper 應用程序編程介面(API)以及在內核中使用的例子開始。 然後,使用 API 構造一個示例應用程序,以便更好地理解其工作原理與局限。
usermode-helper API
usermode-helper API 是個很簡單的 API,其選項為用戶熟知。例如,要創建一個用戶空間進程,通常只要設置名稱為 executable,選項都為 executable,以及一組環境變數(指向 execve 主頁)。創建內核進程也是一樣。但由於創建內核空間進程,還需要設置一些額外選項。
內核版本
本文探討的是 2.6.27 版內核的 usermode-helper API。
表 1 展示的是 usermode-helper API 中一組關鍵的內核函數
表 1. usermode-helper API 中的核心函數
API 函數
描述
call_usermodehelper_setup 准備 user-land 調用的處理函數
call_usermodehelper_setkeys 設置 helper 的會話密鑰
call_usermodehelper_setcleanup 為 helper 設置一個清空函數
call_usermodehelper_stdinpipe 為 helper 創建 stdin 管道
call_usermodehelper_exec 調用 user-land
表 2 中還有一些簡化函數,它們封裝了的幾個內核函數(用一個調用代替多個調用)。這些簡化函數在很多情況下都很有用,因此盡可能使用他們。
表 2. usermode-helper API 的簡化
API 函數
描述
call_usermodehelper 調用 user-land
call_usermodehelper_pipe 使用 stdin 管道調用 user-land
call_usermodehelper_keys 使用會話密鑰調用 user-land
讓我們先瀏覽一遍這些核心函數,然後探索簡化函數提供了哪些功能。核心 API 使用了一個稱為subprocess_info 結構的處理函數引用進行操作。該結構(可在 ./kernel/kmod.c 中找到)集合了給定的 usermode-helper 實例的所有必需元素。該結構引用從 call_usermodehelper_setup 調用返回。該結構(以及後續調用)將會在 call_usermodehelper_setkeys(用於存儲憑證)、call_usermodehelper_setcleanup 以及 call_usermodehelper_stdinpipe 的調用中進一步配置。最後,一旦配置完成,就可通過調用 call_usermodehelper_exec 來調用配置好的用戶模式應用程序。
聲明
該方法提供了一個從內核調用用戶空間應用程序必需的函數。盡管這項功能有合理用途,還應仔細考慮是否需要其他實現。這是一個方法,但其他方法會更合適。
核心函數提供了最大程度的控制,其中 helper 函數在單個調用中完成了大部分工作。管道相關調用(call_usermodehelper_stdinpipe 和 helper 函數 call_usermodehelper_pipe)創建了一個相聯管道供 helper 使用。具體地說,創建了管道(內核中的文件結構)。用戶空間應用程序對管道可讀,內核對管道可寫。對於本文,核心轉儲只是使用 usermode-helper 管道的應用程序。在該應用程序(./fs/exec.c do_coremp())中,核心轉儲通過管道從內核空間寫到用戶空間。
這些函數與 sub_processinfo 以及 subprocess_info 結構的細節之間的關系如圖 1 所示。
圖 1. Usermode-helper API 關系
表 2 中的簡化函數內部執行 call_usermodehelper_setup 函數和 call_usermodehelper_exec 函數。表 2 中最後兩個調用分別調用的是 call_usermodehelper_setkeys 和 call_usermodehelper_stdinpipe。可以在 ./kernel/kmod.c 找到 call_usermodehelper_pipe 和 call_usermodehelper 的代碼,在 ./include/linux/kmod.h 中找到 call_usermodhelper_keys 的代碼。
為什麼要從內核調用用戶空間應用程序?
現在讓我們看一看 usermode-helper API 所使用的內核空間。表 3 提供的並不是專門的應用程序列表,而是一些有趣應用的示例。
表 3. 內核中的 usermode-helper API 應用程序
應用程序
源文件位置
內核模塊調用 ./kernel/kmod.c
電源管理 ./kernel/sys.c
控制組 ./kernel/cgroup.c
安全密匙生成 ./security/keys/request_key.c
內核事件交付 ./lib/kobject_uevent.c
最直接的 usermode-helper API 應用程序是從內核空間載入內核模塊。request_mole 函數封裝了 usermode-helper API 的功能並提供了簡單的介面。在一個常用的模塊中,內核指定一個設備或所需服務並調用 request_mole 來載入模塊。通過使用 usermode-helper API,模塊通過 modprobe 載入到內核(應用程序通過 request_mole 在用戶空間被調用)。
與模塊載入類似的應用程序是設備熱插拔(在運行時添加或刪除設備)。該特性是通過使用 usermode-helper API,調用用戶空間的 /sbin/hotplug 工具實現的。
關於 usermode-helper API 的一個有趣的應用程序(通過 request_mole) 是文本搜索 API(./lib/textsearch.c)。該應用程序在內核中提供了一個可配置的文本搜索基礎架構。該應用程序使用 usermode-helper API 將搜索演算法當作可載入模塊進行動態載入。在 2.6.30 內核版本中,支持三個演算法,包括 Boyer-Moore(./lib/ts_bm.c),簡單固定狀態機方法(./lib/ts_fsm.c),以及 Knuth-Morris-Pratt 演算法(./lib/ts_kmp.c)。
usermode-helper API 還支持 Linux 按照順序關閉系統。當需要系統關閉電源時,內核調用用戶空間的 /sbin/poweroff 命令來完成。其他應用程序如 表 3 所示,表中附有其源文件位置。
Usermode-helper API 內部
在 kernel/kmod.c 中可以找到 usermode-helper API 的源代碼 和 API(展示了主要的用作內核空間的內核模塊載入器)。這個實現使用 kernel_execve 完成臟工作(dirty work)。請注意 kernel_execve是在啟動時開啟 init 進程的函數,而且未使用 usermode-helper API。
usermode-helper API 的實現相當簡單直觀(見圖 2)。usermode-helper 從調用call_usermodehelper_exec 開始執行(它用於從預先配置好的 subprocess_info 結構中清除用戶空間應用程序)。該函數接受兩個參數:subprocess_info 結構引用和一個枚舉類型(不等待、等待進程中止及等待進程完全結束)。subprocess_info(或者是,該結構的 work_struct 元素)然後被壓入工作隊列(khelper_wq),然後隊列非同步執行調用。
圖 2. usermode-helper API 內部實現
當一個元素放入 khelper_wq 時,工作隊列的處理函數就被調用(本例中是__call_usermodehelper),它在 khelper 線程中運行。該函數從將 subprocess_info 結構出隊開始,此結構包含所有用戶空間調用所需信息。該路徑下一步取決於 wait 枚舉變數。如果請求者想要等整個進程結束,包含用戶空間調用(UMH_WAIT_PROC)或者是根本不等待(UMH_NO_WAIT),那麼會從 wait_for_helper 函數創建一個內核線程。否則,請求者只是等待用戶空間應用程序被調用(UMH_WAIT_EXEC),但並不完全。這種情況下,會為____call_usermodehelper() 創建一個內核線程。
在 wait_for_helper 線程中,會安裝一個 SIGCHLD 信號處理函數,並為 ____call_usermodehelper 創建另一個內核線程。但在 wait_for_helper 線程中,會調用 sys_wait4 來等待____call_usermodehelper 內核線程(由 SIGCHLD 信號指示)結束。然後線程執行必要的清除工作(為UMH_NO_WAIT 釋放結構空間或簡單地向 call_usermodehelper_exec() 回送一個完成報告)。
函數 ____call_usermodehelper 是實際讓應用程序在用戶空間啟動的地方。該函數首先解鎖所有信號並設置會話密鑰環。它還安裝了 stdin 管道(如果有請求)。進行了一些安裝以後,用戶空間應用程序通過 kernel_execve(來自 kernel/syscall.c)被調用,此文件包含此前定義的 path、argv 清單(包含用戶空間應用程序名稱)以及環境。當該進程完成後,此線程通過調用 do_exit() 而產生。
該進程還使用了 Linux 的 completion,它是像信號一樣的操作。當 call_usermodehelper_exec 函數被調用後,就會聲明 completion。當 subprocess_info 結構放入 khelper_wq 後,會調用wait_for_completion(使用 completion 變數作為參數)。請注意此變數會存儲到 subprocess_info 結構作為 complete 欄位。當子線程想要喚醒 call_usermodehelper_exec 函數,會調用內核方法complete,並判斷來自 subprocess_info 結構的 completion 變數。該調用會解鎖此函數使其能繼續。可以在 include/linux/completion.h 中找到 API 的實現。
應用程序示例
現在,讓我們看看 usermode-helper API 的簡單應用。首先看一下標准 API,然後學習如何使用 helper 函數使事情更簡單。
在該例中,首先開發了一個簡單的調用 API 的可載入內核模塊。清單 1 展示了樣板模塊功能,定義了模塊入口和出口函數。這兩個函數根據模塊的 modprobe(模塊入口函數)或 insmod(模塊入口函數),以及 rmmod(模塊出口函數)被調用。
清單 1. 模塊樣板函數
#include
#include
#include
MODULE_LICENSE( "GPL" );
static int __init mod_entry_func( void )
{
return umh_test();
}
static void __exit mod_exit_func( void )
{
return;
}
mole_init( mod_entry_func );
mole_exit( mod_exit_func );
usermode-helper API 的使用如 清單 2 所示,其中有詳細描述。函數開始是聲明所需變數和結構。以subprocess_info 結構開始,它包含所有的執行用戶空間調用的信息。該調用在調用call_usermodehelper_setup 時初始化。下一步,定義參數列表,使 argv 被調用。該列表與普通 C 程序中的 argv 列表類似,定義了應用程序(數組第一個元素)和參數列表。需要 NULL 終止符來提示列表末尾。請注意這里的 argc 變數(參數數量)是隱式的,因為 argv 列表的長度已經知道。該例中,應用程序名是 /usr/bin/logger,參數是 help!,然後是 NULL 終止符。下一個所需變數是環境數組(envp)。該數組是一組定義用戶空間應用程序執行環境的參數列表。本例中,定義一些常用的參數,這些參數用於定義 shell 並以 NULL 條目結束。
清單 2. 簡單的 usermode_helper API 測試
static int umh_test( void )
{
struct subprocess_info *sub_info;
char *argv[] = { "/usr/bin/logger", "help!", NULL };
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };
sub_info = call_usermodehelper_setup( argv[0], argv, envp, GFP_ATOMIC );
if (sub_info == NULL) return -ENOMEM;
return call_usermodehelper_exec( sub_info, UMH_WAIT_PROC );
}
下一步,調用 call_usermodehelper_setup 來創建已初始化的 subprocess_info 結構。請注意使用了先前初始化的變數以及指示用於內存初始化的 GFP 屏蔽第四個參數。在安裝函數內部,調用了kzalloc(分配內核內存並清零)。該函數需要 GFP_ATOMIC 或 GFP_KERNEL 標志(前者定義調用不可以休眠,後者定義可以休眠)。快速測試新結構(即,非 NULL)後,使用 call_usermodehelper_exec 函數繼續調用。該函數使用 subprocess_info 結構以及定義是否等待的枚舉變數(在 「Usermode-helper API 內部」 一節中有描述)。全部完成! 模塊一旦載入,就可以在 /var/log/messages 文件中看到信息。
還可以通過 call_usermodehelper API 函數進一步簡化進程,它同時執行 call_usermodehelper_setup和 call_usermodehelper_exec 函數。如清單 3 所示,它不僅刪除函數,還消除了調用者管理subprocess_info 結構的必要性。
清單 3. 更簡單的 usermode-helper API 測試
static int umh_test( void )
{
char *argv[] = { "/usr/bin/logger", "help!", NULL };
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };
return call_usermodehelper( argv[0], argv, envp, UMH_WAIT_PROC );
}
請注意在清單 3 中,有著同樣的安裝並調用(例如初始化 argv 和 envp 數組)的需求。此處惟一的區別是 helper 函數執行 setup 和 exec 函數。
⑹ 在linux內核編程里「static int hello_init(void)」是什麼意思
這應該是驅動編程里的例子吧,這是驅動入口函數,你載入驅動後,就先執行這個函數.
⑺ linux源碼分析
linux的tcp-ip棧代碼的詳細分析
1.數據結構(msghdr,sk_buff,socket,sock,proto_ops,proto)
bsd套接字層,操作的對象是socket,數據存放在msghdr這樣的數據結構:
創建socket需要傳遞family,type,protocol三個參數,創建socket其實就是創建一個socket實例,然後創建一個文件描述符結構,並且互相建立一些關聯,即建立互相連接的指針,並且初始化這些對文件的寫讀操作映射到socket的read,write函數上來。
同時初始化socket的操作函數(proto_ops結構),如果傳入的type參數是STREAM類型,那麼就初始化為SOCKET->ops為inet_stream_ops,如果是DGRAM類型,則SOCKET-ops為inet_dgram_ops。對於inet_stream_ops其實是一個結構體,包含了stream類型的socket操作的一些入口函數,在這些函數里主要做的是對socket進行相關的操作,同時通過調用下面提到的sock中的相關操作完成socket到sock層的傳遞。比如在inet_stream_ops里有個inet_release的操作,這個操作除了釋放socket的類型空間操作外,還通過調用socket連接的sock的close操作,對於stream類型來說,即tcp_close來關閉sock
釋放sock。
創建socket同時還創建sock數據空間,初始化sock,初始化過程主要做的事情是初始化三個隊列,receive_queue(接收到的數據包sk_buff鏈表隊列),send_queue(需要發送數據包的sk_buff鏈表隊列),backlog_queue(主要用於tcp中三次握手成功的那些數據包,自己猜的),根據family、type參數,初始化sock的操作,比如對於family為inet類型的,type為stream類型的,sock->proto初始化為tcp_prot.其中包括stream類型的協議sock操作對應的入口函數。
在一端對socket進行write的過程中,首先會把要write的字元串緩沖區整理成msghdr的數據結構形式(參見linux內核2.4版源代碼分析大全),然後調用sock_sendmsg把msghdr的數據傳送至inet層,對於msghdr結構中數據區中的每個數據包,創建sk_buff結構,填充數據,掛至發送隊列。一層層往下層協議傳遞。一下每層協議不再對數據進行拷貝。而是對sk_buff結構進行操作。
⑻ Linux內核中select,poll和epoll的區別
隨著2.6內核對epoll的完全支持,網路上很多的文章和示例代碼都提供了這樣一個信息:使用epoll代替傳統的poll能給網路服務應用帶來性能上的提升。但大多文章里關於性能提升的原因解釋的較少,這里我將試分析一下內核(2.6.21.1)代碼中poll與epoll的工作原理,然後再通過一些測試數據來對比具體效果。
POLL:
先說poll,poll或select為大部分Unix/Linux程序員所熟悉,這倆個東西原理類似,性能上也不存在明顯差異,但select對所監控的文件描述符數量有限制,所以這里選用poll做說明。
poll是一個系統調用,其內核入口函數為sys_poll,sys_poll幾乎不做任何處理直接調用do_sys_poll,do_sys_poll的執行過程可以分為三個部分:
1,將用戶傳入的pollfd數組拷貝到內核空間,因為拷貝操作和數組長度相關,時間上這是一個O(n)操作,這一步的代碼在do_sys_poll中包括從函數開始到調用do_poll前的部分。
2,查詢每個文件描述符對應設備的狀態,如果該設備尚未就緒,則在該設備的等待隊列中加入一項並繼續查詢下一設備的狀態。查詢完所有設備後如果沒有一個設備就緒,這時則需要掛起當前進程等待,直到設備就緒或者超時,掛起操作是通過調用schele_timeout執行的。設備就緒後進程被通知繼續運行,這時再次遍歷所有設備,以查找就緒設備。這一步因為兩次遍歷所有設備,時間復雜度也是O(n),這裡面不包括等待時間。相關代碼在do_poll函數中。
3,將獲得的數據傳送到用戶空間並執行釋放內存和剝離等待隊列等善後工作,向用戶空間拷貝數據與剝離等待隊列等操作的的時間復雜度同樣是O(n),具體代碼包括do_sys_poll函數中調用do_poll後到結束的部分。
EPOLL:
接下來分析epoll,與poll/select不同,epoll不再是一個單獨的系統調用,而是由epoll_create/epoll_ctl/epoll_wait三個系統調用組成,後面將會看到這樣做的好處。
先來看sys_epoll_create(epoll_create對應的內核函數),這個函數主要是做一些准備工作,比如創建數據結構,初始化數據並最終返回一個文件描述符(表示新創建的虛擬epoll文件),這個操作可以認為是一個固定時間的操作。
epoll是做為一個虛擬文件系統來實現的,這樣做至少有以下兩個好處:
1,可以在內核里維護一些信息,這些信息在多次epoll_wait間是保持的,比如所有受監控的文件描述符。
2, epoll本身也可以被poll/epoll;
具體epoll的虛擬文件系統的實現和性能分析無關,不再贅述。
在sys_epoll_create中還能看到一個細節,就是epoll_create的參數size在現階段是沒有意義的,只要大於零就行。
接著是sys_epoll_ctl(epoll_ctl對應的內核函數),需要明確的是每次調用sys_epoll_ctl只處理一個文件描述符,這里主要描述當op為EPOLL_CTL_ADD時的執行過程,sys_epoll_ctl做一些安全性檢查後進入ep_insert,ep_insert里將 ep_poll_callback做為回掉函數加入設備的等待隊列(假定這時設備尚未就緒),由於每次poll_ctl只操作一個文件描述符,因此也可以認為這是一個O(1)操作
ep_poll_callback函數很關鍵,它在所等待的設備就緒後被系統回掉,執行兩個操作:
1,將就緒設備加入就緒隊列,這一步避免了像poll那樣在設備就緒後再次輪詢所有設備找就緒者,降低了時間復雜度,由O(n)到O(1);
2,喚醒虛擬的epoll文件;
最後是sys_epoll_wait,這里實際執行操作的是ep_poll函數。該函數等待將進程自身插入虛擬epoll文件的等待隊列,直到被喚醒(見上面ep_poll_callback函數描述),最後執行ep_events_transfer將結果拷貝到用戶空間。由於只拷貝就緒設備信息,所以這里的拷貝是一個O(1)操作。
還有一個讓人關心的問題就是epoll對EPOLLET的處理,即邊沿觸發的處理,粗略看代碼就是把一部分水平觸發模式下內核做的工作交給用戶來處理,直覺上不會對性能有太大影響,感興趣的朋友歡迎討論。
POLL/EPOLL對比:
表面上poll的過程可以看作是由一次epoll_create/若干次epoll_ctl/一次epoll_wait/一次close等系統調用構成,實際上epoll將poll分成若幹部分實現的原因正是因為伺服器軟體中使用poll的特點(比如Web伺服器):
1,需要同時poll大量文件描述符;
2,每次poll完成後就緒的文件描述符只佔所有被poll的描述符的很少一部分。
3,前後多次poll調用對文件描述符數組(ufds)的修改只是很小;
⑼ Linux的KVM模塊中,kvm_main.c沒有入口函數,不調用宏mole_init是怎麼生成kvm.ko模塊的
很明顯,這個kvm.ko不是virt/kvm/生成的。
而是在 arch/(xxx)/kvm/生成的
⑽ linux多進程,每個進程都有1個main函數嗎
簡單的實現,沒有添加同步機制,回頭再添加上去,而且,我是在不同終端裡面寫的,你可以把兩段代碼,一個放到父進程,一個放到子進程...就可以了你說的這些API,自己man 一次,看看說明就知道用法了.... 樓上說的對齊的問題,我沒有太注意..不過,不管你要共享什麼,一個sizeof看看大小,一個memcpy拷貝,你就作為數據直接拷貝到共享內存區域就OK了...另外一邊再拷貝回來,用一個結構體類型的指針指向你拷貝回來的數據,不就給這部分內存再規劃成一個結構體了。。至於具體的, KEY 的含義,你需要了解linux的ipc機制。 #include #include #include #include #define BUF_SIZE 100 #define KEY 99 int main(void) { int shmid; char *shmptr; shmid=shmget(99,BUF_SIZE,IPC_CREAT|0666); if(shmid==-1) { printf("Shared Memory Created error...\n");exit(0); } shmptr=shmat(shmid,NULL,0); if(shmptr==(void*)-1) { printf("shmat error,shmptr= %d \n",shmptr); exit(1); } while(1) { printf("type strings into Shared Memory:"); fgets(shmptr,BUF_SIZE,stdin); } return 0; } 下面這段就每隔10秒鍾掃描共享內存區域的內容: #include #include #include #include #define BUF_SIZE 100 #define KEY 99 int main(void) { int shmid; char *shmptr; shmid=shmget(99,BUF_SIZE,IPC_CREAT|0666); if(shmid==-1) { printf("Shared Memory Created error...\n");exit(0); } shmptr=shmat(shmid,NULL,0); if(shmptr==(void*)-1) { printf("shmat error,shmptr= %d \n",shmptr); exit(1); } while(1) { printf("Infomation in Shared Memory:"); printf("%s \n",shmptr); sleep(10); } return 0; }