1. 程式人生 > >【驅動】第1課、字元裝置驅動

【驅動】第1課、字元裝置驅動

=====節一、字元裝置驅動程式之概念介紹=====
1、模組(即某單一驅動)是如何構建的?
答:構建一個最基本的驅動模組,只需要4函式+1標頭檔案:模組裝載函式xx_init(), 模組解除安裝函式xx_exit(), module_init(), module_exit(), <linux/init.h, module.h>;
如此,用Makefile編譯,即可生成一個xx.ko這個最基本的且可以編譯成功並在單板平臺載入/解除安裝/執行的驅動模組,在單板串列埠控制檯即可執行載入/解除安裝任務。

2、模組(即某單一驅動)為何可以如此構建使用?即linux核心給模組(即驅動)的構建,掛接,解除安裝提供了什麼支援?

3、應用是怎麼呼叫驅動的?模組(即某單一驅動)是如何工作的?需要什麼樣的環境?比如:需要什麼樣的編譯器,Makefile,C庫函式,?
答:應用最終是讀/寫記憶體,通過 open, read, write 等標準介面呼叫驅動程式的;
open, read, write 等函式是在C庫中實現的;
open, read, write 等標準介面通過 swi_irq指令,即軟中斷進入異常管理模式,進入特權模式,處理異常的程式操作(只有超級使用者才有權載入和解除安裝模組);
驅動的本質就是操作暫存器的程式碼;

4、字元裝置驅動程式如何分析?
答:沿著資料流向,情景分析;

5、作業系統和CPU的關係?編譯工具鏈gcc和arm-linux-gcc的區別和編譯出的檔案的區別?
問題來源:編譯出來的xx.ko驅動模組,可以在單板平臺執行,卻不能在Linux伺服器的遠端伺服器終端執行,是因為伺服器和單板上的作業系統不是一個版本嗎?
或者說同一程式由不同的編譯工具鏈編譯出來的二進位制程式碼是不同的,只能在指定平臺執行?
答:


=====節二~七、LED驅動,按鍵驅動=====
1、驅動載入並使用的三個基本函式/命令的作用?
register_chrdev(0, "led_1", &led_fops); //驅動模組中; 注:"led_1"應改為:"leds",同裝置的類的名字;
# mknod /dev/huhu c 252 0; //命令;
open("/dev/huhu", O_RDWR); //測試程式中;
答:在載入驅動模組時,函式 register_chrdev(0, "led_1", &led_fops); 是以主裝置號、次裝置號及其file_operations結構體來註冊裝置的,
說是裝置名字“led_1”無用,但是在測試程式中open("/dev/huhu", O_RDWR);開啟裝置又是依據什麼引數呢?看來是字串"/dev/xxx",這是使用命令:
# mknod /dev/huhu c 252 0
建立了一個特殊檔案huhu,並且這個特殊檔案即裝置檔案的主裝置號252,次裝置號0,指定路徑為/dev/huhu,用於作為檔案系統的節點。
由此可知,函式open("/dev/xxx", O_RDWR)內,節點檔案的名字只是作為路徑節點,供連線查詢之用,別無它用,而非單純的說"/dev/huhu"名字不重要!
(注:"/dev/huhu"的名字"huhu"是在mknod命令或者class_device_create()函式內設定的)。
從上可以看出,open()開啟的是已知主裝置號、次裝置號的準確的裝置,而register()函式註冊的裝置的驅動模組只有主裝置號,在該類裝置下只有一個裝置是
沒有問題?但是,當該裝置類下有多個該類裝置的話,該怎麼辦?是因為該類裝置都呼叫同一個裝置模組即 file_operations 結構體嗎?程式碼段:
led_class = class_create(THIS_MODULE, "leds");
led_class_dev = class_device_create(led_class, NULL, mkdev(major, 0), NULL, "huhu2");
上面兩個函式,主要功能是為了使得mdev機制在/dev目錄下自動建立主/次裝置號為(major, 0)的裝置的系統節點檔案"/dev/huhu"提供必要
的系統資訊(包含major, minor, device_name等)。
register_chrdev(0, "led_1", &led_fops);用於註冊某裝置x的驅動程式到核心的字元裝置陣列chrdev;
mknod /dev/huhu c 252 0;用於建立可以連線到裝置x的模組的對外介面結構體的節點檔案:
open("/dev/huhu", O_RDWR);用於開啟的某個裝置,需根據其節點檔案"/dev/xyz",即呼叫其對應的驅動模組。
問題:xxx_init();函式只能載入一個裝置還是一類裝置到核心?open()函式是呼叫一個裝置還是一類裝置?
(4)關於名字的問題,驅動模組的名字"led_1"會在目錄/proc/devices中顯現; 裝置的類別名字會在目錄/sys/class中顯現;
具體裝置的節點檔名字會在目錄/sys/class/裝置類別 , /dev中顯現。
為使正規, 規定如下:驅動模組的名字:"xxx_drv"; 裝置的類別名字: "xxxs"; 具體裝置的節點檔名字同裝置名字:"xxx"。可否?
在myleds檔案系統中的用法如下:
#define DEVICE_NAME "leds" /* 載入模式後,執行”cat /proc/devices”命令看到的裝置名稱 */
ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
leds_class_devs[0] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds"); /* /dev/leds */
leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);

2、LED裝置驅動增加
1)驅動原始碼檔案中:
a. 定義指向暫存器的虛擬地址的空指標;
b. 在xx_init()函式中對映使用的暫存器的實體地址成虛擬地址;
c. 在open, read, write操作中,操作暫存器;
d.
2)驅動測試程式檔案:
函式原型:int main(int argc, char ** argv){ 。。。}
a. open裝置,並根據返回值fd判斷裝置開啟是否成功;
b.
注:第一個驅動模組的學習,重點在框架搭建和各種函式結構體的學習使用!大部分函式結構體的使用查百度百科,百度文庫,華清遠見的文章!

3、函式xx_write()內為什麼要用函式copy_from_user()到本地再使用?使用的引數既然已經通過形參傳入,為什麼不直接使用?
static ssize_t s3c2440_leds_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{ ...
copy_from_user(&val, buf, count);
...
}
答:是因為怕改動原引數嗎?可遮蔽該句程式碼實驗一下嗎?


=====節八、中斷方式查詢按鍵發生=====
1、在中斷申請函式request_irq()中: request_irq(IRQ_EINT19, keys_irq, IRQT_BOTHEDGE, "key_5", &pins_desc[3]);
中斷處理屬性項 irq_flags 設為 IRQT_BOTHEDGE , 因此,按鍵做外部中斷源的中斷觸發方式是雙邊沿觸發。
上機驗證:進行一次按鍵“按下”“鬆開”操作,串列埠終端控制檯會列印字串:key_val = 0x81 兩次,但有時也會列印3次,原因?
答:按鍵抖動嗎?

2、wake_up_interruptible(keys_waitq);的操作原理是什麼?為什麼操作一個看似和程式無關的變數keys_waitq(?)即可喚醒這個中斷處理程序?
休眠程序,休眠的是誰?中斷?驅動?驅動的應用程式?
答:喚醒佇列,觸發程序;
static ssize_t keys_drv_read(struct file *file, const char __user *buf, size_t size, loff_t *ppos)
{
...
/* 休眠程序 */
wait_event_interruptible(keys_waitq, ev_press);
/* 上傳中斷引腳的值 */
copy_to_user(buf, &key_val, 1);
/* 標記程序的休眠標記 */
ev_press = 0;
...
}
上面的程式碼中,休眠和上傳的額函式的前後順序?
當copy_to_user(buf, &key_val, 1);在wait_event_interruptible(keys_waitq, ev_press);之後執行,列印異常:
key_val = 0x40
key_val = 0x10
key_val = 0x11
key_val = 0x10
key_val = 0x11
key_val = 0x10

key_val = 0x11
key_val = 0x40
當如上程式所示,copy_to_user(buf, &key_val, 1);在wait_event_interruptible(keys_waitq, ev_press);之前執行,列印正常:
key_val = 0x10
key_val = 0x11
key_val = 0x10
key_val = 0x11
原因何在?
答:static unsigned char key_val;變數定義時沒有用volatile修飾嗎?

3、雖然已經可以默寫中斷方式的按鍵驅動,但是,對程式內的函式和各種機制(例如:中斷)的詳細工作原理還是一知半解,徒之奈何!


=====節九、poll機制_指定時間查詢按鍵發生=====
1、poll和定時器的作用原理有些像,但是區別是定時器在設定時間到之後必定進入定時器中斷,而poll機制則在poll()函式發生error/成功/超時時,
跳出poll函式並返回一個返回值繼續執行其後的程式。

2、是否就是說,有了poll機制的poll_wait(file, &button_waitq, wait)後,可以省略驅動程式xx_read()中的【函式1】了嗎?
【函式1】wait_event_interruptible(button_waitq, ev_press);
例程:ssize_t forth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{ ...
/* 如果沒有按鍵動作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
/* 如果有按鍵動作, 返回鍵值 */
copy_to_user(buf, &key_val, 1);
...
}
答:是的,經驗證,省略驅動程式xx_read()中的函式:wait_event_interruptible(button_waitq, ev_press) 之後,在串列埠的列印結果一樣:
Time out!
key_val = 0x10
key_val = 0x11
key_val = 0x40
key_val = 0x40

3、問題:系統呼叫open、read、write、poll,與之對應的核心函式為:sys_open、sys_read、sys_write、sys_poll。
但他們和定義的file_operations結構體的.read, .write, .open, .poll元素(函式)有什麼關係?
答:以poll為例,應用程式呼叫函式poll(),則會呼叫核心中的sys_poll函式,進而呼叫:
poll > sys_poll > do_sys_poll > poll_initwait 和 do_poll;(接下)
(接上):poll_initwait > init_poll_funcptr(&pwq->pt, __pollwait); 其中,table->qproc=__pollwait;
(接上):do_poll > for(;;) > do_pollfd > mask=file->f_op->poll(file, pwait) -> __pollwait;
各函式功能說明:
3.1、核心框架:
對於系統呼叫poll或select,它們對應的核心函式都是sys_poll。分析sys_poll,即可理解poll機制。
(1) sys_poll函式位於fs/select.c檔案中,程式碼如下:...它對超時引數稍作處理後,直接呼叫do_sys_poll。
(2) do_sys_poll函式也位於位於fs/select.c檔案中,我們忽略其他程式碼:
(3) poll_initwait函式非常簡單,它初始化一個poll_wqueues變數table:
poll_initwait > init_poll_funcptr(&pwq->pt, __pollwait); > pt->qproc = qproc;
即table->pt->qproc = __pollwait,__pollwait將在驅動的poll函式裡用到。
(4) do_sys_poll函式位於fs/select.c檔案中,程式碼如下:
分析其中的程式碼,可以發現,它的作用如下:
①從02行可以知道,這是個迴圈,它退出的條件為:
a.09行的3個條件之一(count非0,超時、有訊號等待處理); count非0表示04行的do_pollfd至少有一個成功。
b.11、12行:發生錯誤
②重點在do_pollfd函式,後面再分析
③第30行,讓本程序休眠一段時間,注意:應用程式執行poll呼叫後,如果①②的條件不滿足,程序就會進入休眠。
那麼,誰喚醒呢?除了休眠到指定時間被系統喚醒外,還可以被驅動程式喚醒──記住這點,這就是為什麼驅動的poll裡要呼叫
poll_wait的原因,後面分析。
(5) do_pollfd函式位於fs/select.c檔案中,程式碼如下:可見,它就是呼叫我們的驅動程式裡註冊的poll函式。
3.2、驅動程式:
驅動程式裡與poll相關的地方有兩處:一是構造file_operation結構時,要定義自己的poll函式。二是通過poll_wait來呼叫上面說到
的__pollwait函式。
(1) pollwait的程式碼如下:p->qproc就是__pollwait函式,從它的程式碼可知,它只是把當前程序掛入我們驅動程式裡定義的一個佇列裡而已。它的程式碼如下:
執行到驅動程式的poll_wait函式時,程序並沒有休眠,我們的驅動程式裡實現的poll函式是不會引起休眠的。讓程序進入休眠,是前面分析的
do_sys_poll函式的30行“__timeout = schedule_timeout(__timeout)”。
poll_wait只是把本程序掛入某個佇列,應用程式呼叫poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > 我們自己
寫的poll函式後,再呼叫schedule_timeout進入休眠。如果我們的驅動程式發現情況就緒,可以把這個佇列上掛著的程序喚醒。可見,poll_wait
的作用,只是為了讓驅動程式能找到要喚醒的程序。即使不用poll_wait,我們的程式也有機會被喚醒:chedule_timeout(__timeout),只是要
休眠__time_out這段時間。
(2) file->f_op->poll即forth_drv_fops->poll,在本節課程程式中即:forth_drv_poll(){... poll_wait(file, &button_waitq, wait) ...}。
綜述,現在來總結一下poll機制:
1. poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函式註冊一下回調函式__pollwait,它就是我們的驅動程式執行poll_wait
時,真正被呼叫的函式。
2. 接下來執行file->f_op->poll,即我們驅動程式裡自己實現的poll函式
它會呼叫poll_wait把自己掛入某個佇列,這個佇列也是我們的驅動自己定義的;
它還判斷一下裝置是否就緒。
3. 如果裝置未就緒,do_sys_poll裡會讓程序休眠一定時間
4. 程序被喚醒的條件有2:一是上面說的“一定時間”到了,二是被驅動程式喚醒。驅動程式發現條件就緒時,就把“某個佇列”上掛著的程序喚醒,
這個佇列,就是前面通過poll_wait把本程序掛過去的佇列。
5. 如果驅動程式沒有去喚醒程序,那麼chedule_timeout(__timeou)超時後,會重複2、3動作,直到應用程式的poll呼叫傳入的時間到達。


4、問題:poll機制和定時器中斷機制有什麼區別?
答:a.兩個機制都是等待一段時間再工作,但是poll是軟體實現的機制,定時器中斷是硬體實現的機制;
b. 定時器時間是硬性設定,除非發生更高階的異常,否則就會等到定時結束執行定時器中斷函式,而poll則是有條件把該程序在此處【注1】休眠,
喚醒的條件:a.09行的3個條件之一(count非0,超時、有訊號等待處理),count非0表示04行的do_pollfd至少有一個成功。//count=poll()函式返回值。
b.11、12行:發生錯誤
可以檢視上面的摘抄的筆記。
當條件成立時則會退出休眠到應用程式poll()函式之後繼續執行後面的程式。
或者通過驅動程式硬性喚醒休眠,如同第third_drv.c中做的一樣: wake_up_interruptible(&button_waitq);
c. 休眠中途可以因為條件觸發而喚醒程序,且再次休眠時當重新計時,而中斷則雷打不動的嚴格按照設定好的計時一次一次周而復始的進行定時器中斷處理。
d. 定時器中斷屬於異常,poll屬於一種把當前的檔案指標掛到等待佇列的阻塞機制,屬於軟體功能。
注1:“此處”指wait_event_interruptible(button_waitq, ev_press); 或 do_sys_poll函式的30行“__timeout = schedule_timeout(__timeout)”。

5、問題:poll機制和定時器中斷機制有什麼相同處?
答:都是在指定的時間內查詢是否有預想的事件(例如按鍵按下)發生;

6、驅動程式與poll的關係?
答:驅動程式裡與poll相關的地方有兩處:一是構造file_operation結構時,要定義自己的poll函式。二是通過poll_wait來呼叫上面說到
的__pollwait函式。

7、main(int argc, char **argv)
{ fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
printf("can't open!\n");
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1)
{ ret = poll(fds, 1, 5000);
if (ret == 0)
printf("time out\n");
else
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
} } }

問什麼open返回值 fd 可以做引數?fd作用? 其中原理何在?
答:1>fd為檔案描述符,核心(kernel)利用檔案描述符(file descriptor)來訪問檔案。檔案描述符是非負整數。開啟現存檔案或新建檔案時,
核心會返回一個檔案描述符。讀寫檔案也需要使用檔案描述符來指定待讀寫的檔案。是一個非負整數。
檔案描述符是由無符號整數表示的控制代碼,程序使用它來標識開啟的檔案。檔案描述符與包括相關資訊(如檔案的開啟模式、檔案的位置型別、
檔案的初始型別等)的檔案物件相關聯,這些資訊被稱作檔案的上下文。
對於每個程序,作業系統核心在u_block結構中維護檔案描述符表,所有的檔案描述符都在該表中建立索引。

2>與檔案描述符相關的操作:
檔案描述符的生成: open(), open64(), creat(), creat64()...
與單一檔案描述符相關的操作:read(), write(), recv(), send()...
與複數檔案描述符相關的操作:select(), pselect(),poll()...
與檔案描述符表相關的操作: close(),dup()...
改變程序狀態的操作: fchdir(),mmap()...
與檔案加鎖的操作: flock(),fcntl (F_GETLK, F_SETLK and F_SETLKW),lockf()...
與套接字相關的操作: connect(),bind(),listen(),accept()...

8、需要補習的課程?
答:1>字元驅動的深入重新學習; 2>程序的休眠與喚醒; 3>poll機制及其與休眠喚醒的關係; 4>異常與中斷處理;


=====節十、非同步通知_指定時間查詢按鍵發生=====
1、課堂筆記
目的:使用非同步通知機制,當裝置資源可獲得,例如發生按鍵中斷時,呼叫kill_fasync()函式激發相應的訊號xx(例如:SIGIO),
觸發系統呼叫函式signal(),最終執行訊號註冊函式 signal() 中該訊號xx對應的訊號處理函式。
整個非同步通知機制fasync工作流程:
a. 應用程式註冊訊號處理函式: signal(SIGIO, my_signal_fun);
b. 誰發?驅動發訊號給應用程式的系統呼叫函式: signal(SIGIO, my_signal_fun);
c. 發給誰?發給應用程式;
d. 怎麼發?

驅動塊:
1)結構體 butoon_async 在某字元驅動結構體的元素 xx_fops.fasync 對應的函式 fifth_drv_fasync 中被函式
fasync_helper(fd,filp,on,&button_async) 初始化,如此才可使用該結構體 butoon_async,而呼叫該結構體的
函式 kill_fasync() 才能使用。
說明:fasync_helper()是核心做的一個輔助函式,其作用就是初始化結構體 butoon_async。
2)本例程中,按鍵按下,觸發中斷處理程式 buttons_irq(),其內kill_fasync()函式將傳送訊號 SIGIO 給結構體
butoon_async 中包含的程序號PID為xx的程序,該訊號將觸發應用程式中的訊號處理函式signal(),並根據接收到
的訊號 SIGIO 執行其對應的訊號處理函式 my_signal_fun() 函式。

應用塊:
1)註冊訊號處理函式 signal(SIGIO, my_signal_fun),給訊號 SIGIO 掛接一個處理函式 my_signal_fun(), 當外部
傳送訊號 SIGIO 給系統呼叫函式 signal() 時,就會執行函式 my_signal_fun。
2)執行以下三步:
fcntl(fd, F_SETOWN, getpid()); // 應用程式通過該指令告訴驅動程式,訊號 SIGIO 發給哪個程序。
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC); // 改變fasync標記,最終會呼叫到驅動的faync > fasync_helper:初始化/釋放fasync_struct結構體。
將初始化驅動程式中的 fasync_struct 結構體,並在該結構體中標註接收該驅動程式傳送的訊號的程序的 PID,即執行該應用程式產生的PID。

綜述,有按鍵按下,觸發按鍵外部中斷,執行中斷處理函式:buttons_irq(),其內執行指令:kill_fasync(&button_async, SIGIO, POLL_IN),
如此,該驅動(即: fifth_drv.ko)將向結構體 button_async 中包含的程序號 PID 代表的程序(即:fifth_drvtest)傳送訊號 SIGIO,
該程序收到訊號 SIGIO,將觸發signal()系統呼叫,並執行 SIGIO 對應的訊號處理函式 my_signal_fun()。


=====節十一、同步,互斥,阻塞=====
目的:同一時刻,只能有一個應用程式開啟驅動程式: /dev/buttons。

1.用訊號量的方法,實現同一時刻,只能有一個應用程式開啟驅動程式: /dev/buttons 的目的。
問題:為什麼執行命令:kill -9 846,殺掉PID為846的程序 ./sixthdrvtest後,正處在休眠等待獲得訊號量的
另一個程序(例如:第二次執行應用程式./sixthdrvtest產生的另一個程序)就會立馬獲得訊號量並且執行?
期間發生了什麼?訊號量不是隻有執行命令:up(&button_lock) 才能釋放訊號量嗎?是否程序被kill之後,
其所有的資料都被清除?
答:不知。

2、用非阻塞操作和訊號量的方法,進行試驗時,發現不同於視訊的試驗資料:
有明顯的按鍵值列印延遲,且5秒之後沒有自動列印!原因?

3、定時器按鍵防抖動
問題1:定時器 xx_timer 觸發的條件是計數系統中斷次數的全域性變數 jiffies 相等於還是大於等於定時器的超時時間 xx_timer.expires ?
定時器 xx_timer 是為了給按鍵中斷消抖用的,若是以上兩種觸發的任意一種,該怎樣防止多餘的定時器中斷?
答:未查到具體使用說明。但根據視訊的列印結果顯示:執行語句add_timer()之後,程式會立馬執行該定時器的中斷處理函式。
另,按鍵之後“按下”“鬆開”兩個列印,不按鍵時沒有列印。
問題2:根據下面的資料,mod_timer(&buttons_timer, jiffies+HZ/100)中的 jiffies+HZ/100 即 xx_timer.expires ,但是執行add_timer()
產生的第一次定時器中斷的原因是什麼?
【資料:】
1、核心定時器的排程函式執行過一次後就不會再被運行了(相當於自動登出),但可以通過在被排程的函式中重新排程自己來週期執行。
在SMP系統中,排程函式總是在註冊它的同一CPU上執行,以儘可能獲得快取的局域性。
2、int timer_pending(const struct timer_list *timer) 這個函式用來判斷一個定時器是否被新增到了核心連結串列中以等待被排程執行。
注意,當一個定時器函式即將要被執行前,核心會把相應的定時器從核心連結串列中刪除(相當於登出)。
【例程:】
File: buttons.c
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms後啟動定時器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100);
printk("buttons_irq--0: jiffies = %ld, buttons_timer.expires = %ld\n", jiffies, buttons_timer.expires);
return IRQ_RETVAL(IRQ_HANDLED);
}
...
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
printk("buttons_timer_function--1: jiffies = %ld, buttons_timer.expires = %ld\n", jiffies, buttons_timer.expires);
if (!pindesc) // 無此語句,程式將崩潰!
return;
printk("buttons_timer_function--2: jiffies = %ld, buttons_timer.expires = %ld\n", jiffies, buttons_timer.expires);
pinval = s3c2410_gpio_getpin(pindesc->pin);
...
}
...
static int sixth_drv_init(void)
{
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
//buttons_timer.expires = 0;
add_timer(&buttons_timer);
printk("sixth_drv_init: jiffies = %ld, buttons_timer.expires = %ld\n", jiffies, buttons_timer.expires);
...
}
File: buttons_test.c
int main(int argc, char **argv)
{...
fd = open("/dev/buttons", O_RDWR); //若改為: O_RDWR|O_NONBLOCK;則程式崩潰,不停的列印:key_val: 0x0, ret = -1
}
【列印結果:】
# insmod buttons.ko
sixth_drv_init: jiffies = 329832, buttons_timer.expires = 0
buttons_timer_function--1: jiffies = 329833, buttons_timer.expires = 0
#
# ./buttons_test &
#
# buttons_irq--0: jiffies = 336155, buttons_timer.expires = 336157
buttons_timer_function--1: jiffies = 336157, buttons_timer.expires = 336157
buttons_timer_function--2: jiffies = 336157, buttons_timer.expires = 336157
key_val: 0x1, ret = 1
buttons_irq--0: jiffies = 336185, buttons_timer.expires = 336187
buttons_timer_function--1: jiffies = 336187, buttons_timer.expires = 336187
buttons_timer_function--2: jiffies = 336187, buttons_timer.expires = 336187
key_val: 0x81, ret = 1
<結束>
問題3:載入模組和執行應用程式時,使用下面的程式碼將造成程式崩潰!
(1)if (!pindesc) // 無此語句,程式將崩潰!
return;
(2)fd = open("/dev/buttons", O_RDWR); //若改為: O_RDWR|O_NONBLOCK;則程式崩潰,不停的列印:key_val: 0x0, ret = -1
原因何在?
答:不知。