解析Linux內核的根本的模塊治理與時光治理操作。本站提示廣大學習愛好者:(解析Linux內核的根本的模塊治理與時光治理操作)文章只能為提供參考,不一定能成為您想要的結果。以下是解析Linux內核的根本的模塊治理與時光治理操作正文
內核模塊治理
Linux裝備驅動會之內核模塊的情勢湧現,是以學會編寫Linux內核模塊編程是進修linux裝備驅動的先決前提。
Linux內核的全體構造異常宏大,其包括的組件異常多。我們把須要的功效都編譯到linux內核,以模塊方法擴大內核功效。
先來看下最簡略的內核模塊
#include <linux/init.h> #include <linux/module.h> static int __init hello_init(void) { printk(KERN_ALERT "Hello world! %s, %d\n", __FILE__, __LINE__); return 0; } static void __exit hello_exit(void) { printk(KERN_ALERT "Hello world! %s, %d\n", __FILE__, __LINE__); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Mikcy Liu"); MODULE_DESCRIPTION("A simple Module"); MODULE_ALIAS("a simple module");
頭文件init.h包括了宏_init和_exit,它們許可釋放內核占用的內存。
module_init()和hello_exit()是模塊編程中最根本也是必需的兩個函數。
module_init()是驅動法式初始化的進口點。
hello_exit是模塊的加入和清算函數。此處可以做一切終止該驅動法式時相干的清算任務。
內核模塊頂用於輸入的函數式內核空間的printk()而非用戶空間的printf(),printk()的用法和printf()類似,但前者可界說輸入級別。printk()可作為一種最根本的內核調試手腕
前者可以界說輸入級別,在 <內核目次>/include/linux/kernel.h中
#define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant condition */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */
未設定級其余,在<內核目次>/kernel/printk.c中界說
/* printk's without a loglevel use this.. */ #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */ #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */
只要當printk打印信息時的loglevel小於DEFAULT_CONSOLE_LOGLEVEL的值(優先級高於console loglevel),這些信息才會被打印到console上。
模塊聲明與描寫
模塊編譯
起首看看Makefile文件:
obj-m := hello.o KERNEL_BUILD := /lib/modules/$(shell uname -r)/build all: make -C $(KERNEL_BUILD) M=$(shell pwd) modules clean: -rm -rf *.o *.ko *.mod.c .*.cmd *.order *.symvers .tmpversions KERNELBUILD :=/lib/modules/$(shell uname -r)/
build是編譯內核模塊須要的Makefile的途徑,Ubuntu下是/lib/modules/2.6.31-14-generic/build
假如是Arm平台的開辟板,則-C選項指定的地位(即內核源代碼目次),個中保留有內核的頂層Makefile文件.
make -C $(KERNEL_BUILD) M=$(shell pwd) modules 編譯內核模塊。-C 將任務目次轉到KERNEL_BUILD,挪用該目次下的Makefile,並向這個Makefile傳遞參數M的值是$(shell pwd) modules。
M=選項讓該makefile在結構modules目的之前前往到模塊源代碼目次。然後modules目的指向obj-m變量中設定的模塊
履行make敕令開端編譯模塊,生成hello.ko,履行make clean可消除編譯發生的文件。
1、添加模塊
insmod hello.ko
2、檢查模塊
lsmod | grep hello
lsmod敕令現實上讀取並剖析/proc/modules文件,也能夠cat /proc/modules文件
在模塊地點目次下履行
modinfo hello.ko可以檢查模塊信息,以下所示
filename: hello.ko alias: a simple module description: A simple Module author: Mikcy Liu license: GPL srcversion: 875C95631F4F336BBD4216C depends: vermagic: 3.5.0-17-generic SMP mod_unload modversions 686
3、刪除模塊
rmmod hello
模塊加載函數
Linux內核模塊加載函數普通以__init標識聲明,典范的模塊加載函數的情勢以下:
static int __init initialization_function(void) { //初始化代碼 } module_init(initialization_function);
模塊加載函數必需以“module_init(函數名)”的情勢指定。它前往整形值,若初始化勝利,應前往0。而在初始化掉敗時。應當前往毛病編碼。
在linux內核裡,毛病編碼是一個負值,在<linux/errno.h>中界說,包括-ENODEV、-ENOMEM之類的符號值。前往響應的毛病編碼是種異常好的習氣,由於只要如許,用戶法式才可以應用perror等辦法把它們轉換成成心義的毛病信息字符串。
在linux2.6內核中,一切標識為__init的函數在銜接的時刻都邑放在.init.text(這是module_init宏在目的代碼中增長的一個特別區段,用於解釋內核初始化函數的地點地位)這個區段中,另外,一切的__init函數在區段.initcall.init中還保留著一份函數指針,在初始化時內核會經由過程這些函數指針挪用這些__init函數,並在初始化完成後釋放init區段(包含.init.text和.initcall.init等)。所以年夜家應留意不要在停止初始化後仍要應用的函數上應用這個標志。
模塊卸載函數
Linux內核卸載模塊函數普通以__exit標識聲明,典范的模塊卸載函數的情勢以下:
static void __exit cleanup_function(void) { //釋放代碼 } module_exit(cleanup_function);
模塊卸載函數在模塊卸載時被挪用,不前往任何值,必需以”module_exit(函數名)”的情勢來指定
與__init一樣__exit也能夠使對應函數在運轉完成後主動收受接管內存。
普通來講,模塊卸載函數完成與模塊加載函數相反的功效:
假如模塊加載函數注冊了 XXX模塊,則模塊卸載函數應刊出XXX。
若模塊加載函數動體請求了內存,則模塊卸載函數應釋放該內存。
若模塊加載函數請求了硬件資本,則模塊卸載函數應釋放這些硬件資本。
若模塊加載函數開啟了硬件,則模塊卸載函數應封閉硬件。
內核時光治理
(1)內核中的時光概念
時光治理在linux內核中占領異常主要的感化。
絕對於事宜驅動而言,內核中有年夜量函數是基於時光驅動的。
有些函數是周期履行的,好比每10毫秒刷新一次屏幕;
有些函數是推後必定時光履行的,好比內核在500毫秒後履行某項義務。
要辨別:
*相對時光和絕對時光
*周期性發生的事宜和推延履行的事宜
周期性事宜是由體系體系准時器驅動的
(2)HZ值
內核必需在硬件准時器的贊助下能力盤算和治理時光。
准時器發生中止的頻率稱為節奏率(tick rate)。
在內核中指定了一個變量HZ,內核初始化的時刻會依據這個值肯定准時器的節奏率。
HZ界說在<asm/param.h>,在i386平台上,今朝采取的HZ值是1000。
也就是時鐘中止每秒產生1000次,周期為1毫秒。即:
#define HZ 1000
留意!HZ不是個固定不變的值,它是可以更改的,可以在內核源代碼設置裝備擺設的時刻輸出。
分歧的系統構造其HZ值是紛歧樣的,好比arm就采取100。
假如在驅動中要應用體系的中止頻率,直接應用HZ,而不要用100或1000
a.幻想的HZ值
i386的HZ值一向采取100,直到2.5版後才改成1000。
進步節奏率意味著時鐘中止發生的加倍頻仍,中止處置法式也會更頻仍地履行。
帶來的利益有:
*內審定時器可以或許以更高的頻率和更高的精確度運轉
*依附准時器履行的體系挪用,好比poll()和select(),運轉的精度更高
*進步過程搶占的精確度
(延長了調劑延時,假如過程還剩2ms時光片,在10ms的調劑周期下,過程會多運轉8ms。
因為延誤了搶占,關於一些對時光請求嚴厲的義務會發生影響)
害處有:
*節奏率要高,體系累贅越重。
中止處置法式將占用更多的處置器時光。
(3)jiffies
全局變量jiffies用於記載體系啟動以來發生的節奏的總數。
啟動時,jiffies初始化為0,爾後每次時鐘中止處置法式都邑增長該變量的值。
如許,體系啟動後的運轉時光就是jiffies/HZ秒
jiffies界說於<linux/jiffies.h>中:
extern unsigned long volatile jiffies;
jiffies變量老是為unsigned long型。
是以在32位系統構造上是32位,而在64位系統上是64位。
關於32位的jiffies,假如HZ為1000,49.7天後會溢出。
固然溢出的情形不罕見,但法式在檢測超不時依然能夠由於缭繞而招致毛病。
linux供給了4個宏來比擬節奏計數,它們能准確地處置節奏計數缭繞。
#include <linux/jiffies.h> #define time_after(unknown, known) // unknow > known #define time_before(unknown, known) // unknow < known #define time_after_eq(unknown, known) // unknow >= known #define time_before_eq(unknown, known) // unknow <= known
unknown平日是指jiffies,known是須要比較的值(經常是一個jiffies加減後盤算出的絕對值)
例:
unsigned long timeout = jiffies + HZ/2; /* 0.5秒後超時 */ ... if(time_before(jiffies, timeout)){ /* 沒有超時,很好 */ }else{ /* 超時了,產生毛病 */
time_before可以懂得為假如在超時(timeout)之前(before)完成
*體系中還聲清楚明了一個64位的值jiffies_64,在64位體系中jiffies_64和jiffies是一個值。
可以經由過程get_jiffies_64()取得這個值。
*應用
u64 j2; j2 = get_jiffies_64();
(4)取得以後時光
驅動法式中普通不須要曉得牆鐘時光(也就是年代日的時光)。但驅動能夠須要處置相對時光。
為此,內核供給了兩個構造體,都界說在<linux/time.h>:
a.
struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ };
較老,但很風行。采取秒和毫秒值,保留了1970年1月1日0點以來的秒數
b.
struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };
較新,采取秒和納秒值保留時光。
c.do_gettimeofday()
該函數用平日的秒或微秒來填充一個指向struct timeval的指針變量,原型以下:
#include <linux/time.h> void do_gettimeofday(struct timeval *tv);
d.current_kernel_time()
該函數可用於取得timespec
#include <linux/time.h> struct timespec current_kernel_time(void);
肯定時光的延遲履行
裝備驅動法式常常須要將某些特定代碼延遲一段時光後履行,平日是為了讓硬件能完成某些義務。
擅長准時器周期(也稱為時鐘嘀嗒)的延遲可以經由過程應用體系時鐘完成,而異常短的延時則經由過程軟件輪回的方法完成
(1)短延時
關於那些最多幾十個毫秒的延遲,沒法借助體系准時器。
體系經由過程軟件輪回供給了上面的延遲函數:
#include <linux/delay.h> /* 現實在<asm/delay.h> */ void ndelay(unsigned long nsecs); /*延遲納秒 */ void udelay(unsigned long usecs); /*延遲微秒 */ void mdelay(unsigned long msecs); /*延遲毫秒 */
這三個延遲函數均是忙期待函數,在延遲進程中沒法運轉其他義務。
(2)長延時
a.在延遲到期前讓出處置器
while(time_before(jiffies, j1)) schedule();
在期待時代可讓出處置器,但體系沒法進入余暇形式(由於這個過程一直在停止調劑),晦氣於省電。
b.超時函數
#include <linux/sched.h> signed long schedule_timeout(signed long timeout);
應用方法:
set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(2*HZ); /* 睡2秒 */
過程經由2秒後會被叫醒。假如不願望被用戶空間打斷,可以將過程狀況設置為TASK_UNINTERRUPTIBLE。
#include <linux/init.h> #include <linux/module.h> #include <linux/time.h> #include <linux/sched.h> #include <linux/delay.h> static int __init test_init(void) { set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(5 * HZ); printk(KERN_INFO "Hello Micky\n"); return 0; } static void __exit test_exit(void) { } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Micky Liu"); MODULE_DESCRIPTION("Test for delay");
(3)期待隊列
應用期待隊列也能夠完成長延遲。
在延遲時代,以後過程在期待隊列中睡眠。
過程在睡眠時,須要依據所期待的事宜鏈接到某一個期待隊列。
a.聲明期待隊列
期待隊列現實上就是一個過程鏈表,鏈表中包括了期待某個特定事宜的一切過程。
#include <linux/wait.h> struct __wait_queue_head { spinlock_t lock; struct list_head task_list; }; typedef struct __wait_queue_head wait_queue_head_t;
要想把過程參加期待隊列,驅動起首要在模塊中聲明一個期待隊列頭,並將它初始化。
靜態初始化
DECLARE_WAIT_QUEUE_HEAD(name);
靜態初始化
wait_queue_head_t my_queue; init_waitqueue_head(&my_queue);
b.期待函數
過程經由過程挪用上面函數可以在某個期待隊列中休眠固定的時光:
#include <linux/wait.h> long wait_event_timeout(wait_queue_head_t q,condition, long timeout); long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout);
挪用這兩個函數後,過程會在給定的期待隊列q上休眠,但會在超時(timeout)到期時前往。
假如超時到期,則前往0,假如過程被其他事宜叫醒,則前往殘剩的時光數。
假如沒有期待前提,則將condition設為0
應用方法:
wait_queue_head_t wait; init_waitqueue_head(&wait); wait_event_interruptible_timeout(wait, 0, 2*HZ); /*以後過程在期待隊列wait中睡2秒 */
(4)內審定時器
還有一種將義務延遲履行的辦法是采取內審定時器。
與後面幾種延遲辦法分歧,內審定時器其實不會壅塞以後過程,
啟動一個內審定時器只是聲清楚明了要在將來的某個時辰履行一項義務,以後過程依然持續履行。
不要用准時器完成硬及時義務
准時器由構造timer_list表現,界說在<linux/timer.h>
struct timer_list{ struct list_head entry; /* 准時器鏈表 */ unsigned long expires; /* 以jiffies為單元的准時值 */ spinlock_t lock; void(*function)(unsigned long); /* 准時器處置函數 */ unsigned long data; /* 傳給准時器處置函數的參數 */ }
內核在<linux/timer.h>中供給了一系列治理准時器的接口。
a.創立准時器
struct timer_list my_timer;
b.初始化准時器
init_timer(&my_timer); /* 填湊數據構造 */ my_timer.expires = jiffies + delay; my_timer.data = 0; my_timer.function = my_function; /*准時器到期時挪用的函數*/
c.准時器的履行函數
超時處置函數的原型以下:
void my_timer_function(unsigned long data);
可以應用data參數用一個處置函數處置多個准時器。可以將data設為0
d.激活准時器
add_timer(&my_timer);
准時器一旦激活就開端運轉。
e.更改已激活的准時器的超不時間
mod_timer(&my_timer, jiffies+ney_delay);
可以用於那些曾經初始化但還沒激活的准時器,
假如挪用時准時器未被激活則前往0,不然前往1。
一旦mod_timer前往,准時器將被激活。
f.刪除准時器
del_timer(&my_timer);
被激活或未被激活的准時器都可使用,假如挪用時准時器未被激活則前往0,不然前往1。
不須要為曾經超時的准時器挪用,它們被主動刪除
g.同步刪除
del_time_sync(&my_timer);
在smp體系中,確保前往時,一切的准時器處置函數都加入。不克不及在中止高低文應用。
#include <linux/init.h> #include <linux/module.h> #include <linux/time.h> #include <linux/sched.h> #include <linux/delay.h> #include <linux/timer.h> struct timer_list my_timer; static void timer_handler(unsigned long arg) { printk(KERN_INFO "%s %d Hello Micky! arg=%lu\n",__func__, __LINE__, arg ); } static int __init test_init(void) { init_timer(&my_timer); my_timer.expires = jiffies + 5 * HZ; my_timer.function = timer_handler; my_timer.data = 10; add_timer(&my_timer); return 0; } static void __exit test_exit(void) { del_timer(&my_timer); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Micky Liu"); MODULE_DESCRIPTION("Test for timer"); #include <linux/init.h> #include <linux/module.h> #include <linux/time.h> #include <linux/sched.h> #include <linux/delay.h> #include <linux/timer.h> struct timer_list my_timer; static void timer_handler(unsigned long arg) { printk(KERN_INFO "%s %d Hello Micky! arg=%lu\n",__func__, __LINE__, arg ); } static int __init test_init(void) { init_timer(&my_timer); //my_timer.expires = jiffies + 5 * HZ; my_timer.function = timer_handler; my_timer.data = 10; //add_timer(&my_timer); mod_timer(&my_timer, jiffies + 5 * HZ); return 0; } static void __exit test_exit(void) { del_timer(&my_timer); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Micky Liu"); MODULE_DESCRIPTION("Test for timer");
不肯定時光的延遲履行
(1)甚麼是不肯定時光的延遲
後面引見的是肯定時光的延遲履行,但在寫驅動的進程中常常碰到這類情形:
用戶空間法式挪用read函數從裝備讀數據,但裝備中以後沒有發生數據。
此時,驅動的read函數默許的操作是進入休眠,一向期待到裝備中有了數據為止。
這類期待就是不准時的延遲,平日采取休眠機制來完成。
(2)休眠
休眠是基於期待隊列完成的,後面我們曾經引見過wait_event系列函數,
但如今我們將不會有肯定的休眠時光。
當過程被置入休眠時,會被標志為特別狀況並從調劑器的運轉隊列中移走。
直到某些事宜產生後,如裝備吸收到數據,則將過程從新設為運轉態並進入運轉隊列停止調劑。
休眠函數的頭文件是<linux/wait.h>,詳細的完成函數在kernel/wait.c中。
a.休眠的規矩
*永久不要在原子高低文中休眠
*當被叫醒時,我們沒法曉得睡眠了若干時光,也不曉得醒來後能否取得了我們須要的資本
*除非曉得有其他過程會在其他處所叫醒我們,不然過程不克不及休眠
b.期待隊列的初始化
見前文
c.休眠函數
linux最簡略的睡眠方法為wait_event宏。該宏在完成休眠的同時,檢討過程期待的前提。
A.
void wait_event( wait_queue_head_t q, int condition);
B.
int wait_event_interruptible(wait_queue_head_t q, int condition);
q: 是期待隊列頭,留意是采取值傳遞。
condition: 隨意率性一個布爾表達式,在前提為真之前,過程會堅持休眠。
留意!過程須要經由過程叫醒函數才能夠被叫醒,此時須要檢測前提。
假如前提知足,則被叫醒的過程真正醒來;
假如前提不知足,則過程持續睡眠。
d.叫醒函數
當我們的過程睡眠後,須要由其他的某個履行線程(能夠是另外一個過程或中止處置例程)叫醒。
叫醒函數:
#include <linux/wait.h>
1.
void wake_up( wait_queue_head_t *queue);
2.
void wake_up_interruptible( wait_queue_head_t *queue);
wake_up會叫醒期待在給定queue上的一切過程。
而wake_up_interruptible叫醒那些履行可中止休眠的過程。
理論中,商定做法是在應用wait_event時應用wake_up,而應用wait_event_interruptible時應用wake_up_interruptible。