字符设备驱动程序之按键——同步互斥阻塞
生活随笔
收集整理的這篇文章主要介紹了
字符设备驱动程序之按键——同步互斥阻塞
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
我們知道在之前的應用程序中,如果我們同時運行兩次應用程序的話,則兩次都可以同時打開設備,這就是說我們的按鍵資源同時被兩個進程使用。顯然這不是我們想要的,那么下面我們就要引入互斥的概念。
關于互斥其實其實現很簡單,就是采用一些標志,當文件被一個進程打開后,就會設置該標志,使其他進程無法打開設備文件。下面,我們就完全靠自己去實現一個互斥,代碼修改如下:
首先定義一個全局變量: static int canread=1; 然后在open函數開始處加入如下代碼: ? if(--canread!=0) ? ? ? ? { ? ? ? ? ?? ? ? ? ? ? ? canread++; ? ? ? ? ? ? return -EBUSY; ? ? ? ? } 在close函數里面加入如下代碼: anread++; 我們可以先分析一下,當進程A打開設備文件時,因為初始值canread=1,canread減1后正好等于0,不會出錯返回,成功打開了。此時當進程B再來打開該設備文件時,因canread=0,canread減1不為0,所以會出錯出錯返回,且canread加1,canread=0。當進程A關閉后,canread加1,canread=1;這時如果B進程運行,會成功打開設備文件。看上去似乎完美得實現了互斥,其實這其中是有問題的。因為其實對于: ?--canread! 這行代碼來說,它分為3的步驟,包括:讀取canread值,修改canread值,寫回canread的值。 但是不要忘記了,我們的linux系統是一個多任務的系統,假如當A讀取了canread的值為1時,還沒來得及減1,就已經切換到了進程B,進程B讀出的canread的值也是1,這樣的話,B就成功打開了設備文件。如果這時再切換回了A,因為之前A已經讀出了canread為1,這時會將canread當作1來進行后續處理,也可以打開文件。這可不是我們想要的,那么有沒有其他辦法呢?答案是肯定的,下面來介紹兩種方法:
方法一:原子操作 定義:原子操作指的是在執行過程中不會被別的代碼路徑所中斷的操作。 下面是幾個常用原子操作函數: atomic_t v = ATOMIC_INIT(0); ? ? //定義原子變量v并初始化為0 atomic_read(atomic_t *v); ? ? ? ?//返回原子變量的值 void atomic_inc(atomic_t *v); ? ?//原子變量增加1 void atomic_dec(atomic_t *v); ? ?//原子變量減少1 int atomic_dec_and_test(atomic_t *v); //自減操作后測試其是否為0,為0則返回true,否則返回false。 修改代碼:
首先定義全局原子變量: static atomic_t canread= ATOMIC_INIT(1); 然后在open函數開始處加入如下代碼: ?if(!atomic_dec_and_test(&canread)) ? ? ? ? { ? ? ? ? ?? ? ? ? ? ? ? atomic_inc(&canread); ? ? ? ? ? ? return -EBUSY; ? ? ? ? }
在close函數里面加入如下代碼: atomic_inc(&canread);
方法二:信號量 定義:信號量(semaphore)是用于保護臨界區的一種常用方法,只有得到信號量的進程才能執行臨界區代碼。當獲取不到信號量時,進程進入休眠等待狀態。 下面是幾個常用的信號量相關的函數:
2. 信號量 信號量(semaphore)是用于保護臨界區的一種常用方法,只有得到信號量的進程才能執行臨界區代碼。 當獲取不到信號量時,進程進入休眠等待狀態。 定義信號量 struct semaphore sem; 初始化信號量 void sema_init (struct semaphore *sem, int val); void init_MUTEX(struct semaphore *sem);//初始化為0 static DECLARE_MUTEX(button_lock);?? ? //定義互斥鎖 獲得信號量 void down(struct semaphore * sem); int down_interruptible(struct semaphore * sem);? int down_trylock(struct semaphore * sem); 釋放信號量 void up(struct semaphore * sem); 修改代碼: 首先定義全局互斥鎖: static DECLARE_MUTEX(button_lock); 在open函數中獲得信號量: down(&button_lock); 在close函數中釋放信號量:
up(&button_lock); 我們測試時第一次運行應用程序,發現其狀態時S,第二次運行,其狀態時D,S表示睡眠狀態,是因為沒有按鍵按下而產生睡眠,而D表示一種僵死狀態,或者也可以說是一種睡眠狀態,不過它是因為沒有獲取信號量而進入僵死。那么第二個進程什么時候才能從僵死狀 態喚醒呢?那就要等到第一個進程釋放信號量。我們將第一個進程殺死,發現第二個進程的狀態變成了S,從而驗證了我們的說法。
下面我們再來談一下阻塞和非阻塞的問題: 阻塞操作:是指在執行設備操作時若不能獲得資源則掛起進程,直到滿足可操作的條件后再進行操作。被掛起的進程進入休眠狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。 非阻塞操作:進程在不能進行設備操作時并不掛起,它或者放棄,或者不停地查詢,直至可以進行操作為止。 我們在驅動程序中修改代碼: 在open函數中加入如下代碼: if (file->f_flags & O_NONBLOCK) { if (down_trylock(&button_lock)) return -EBUSY; } else { down(&button_lock); } 我們在read函數中加入如下代碼: if (filp->f_flags & O_NONBLOCK) { if (!ev_press) ?//如果沒有按鍵按下返回 return -EAGAIN; } else { wait_event_interruptible(button_waitq, ev_press);//這一句要將本來的注釋掉 } 將ev_press相關的定義添加上 添加定義:static DECLARE_WAIT_QUEUE_HEAD(button_waitq); 在中斷中補上這一句:wake_up_interruptible(&button_waitq);
測試程序如下: #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <poll.h> #include <signal.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h>
int main(int argc, char **argv) { ? ? ? ? int fd; ? ?? unsigned char key_val; ? ? ? fd = open("/dev/keys", O_RDWR); if (fd < 0) { printf("can't open!\n"); }
while (1) { ?? ?read(fd, &key_val, 1); ?? ?printf("key_val: 0x%x\n", key_val); } return 0; } 默認情況下是阻塞方式打開的,運行測試程序,當我們沒有按下按鍵時會休眠,但不會返回。 我們將: fd = open("/dev/keys", O_RDWR);改為 fd = open("/dev/keys", O_RDWR |?O_NONBLOCK); 會變成非阻塞方式打開,這時沒有按鍵按下會返回。為方便測試,我們在循環里加入:sleep(5);
首先定義一個全局變量: static int canread=1; 然后在open函數開始處加入如下代碼: ? if(--canread!=0) ? ? ? ? { ? ? ? ? ?? ? ? ? ? ? ? canread++; ? ? ? ? ? ? return -EBUSY; ? ? ? ? } 在close函數里面加入如下代碼: anread++; 我們可以先分析一下,當進程A打開設備文件時,因為初始值canread=1,canread減1后正好等于0,不會出錯返回,成功打開了。此時當進程B再來打開該設備文件時,因canread=0,canread減1不為0,所以會出錯出錯返回,且canread加1,canread=0。當進程A關閉后,canread加1,canread=1;這時如果B進程運行,會成功打開設備文件。看上去似乎完美得實現了互斥,其實這其中是有問題的。因為其實對于: ?--canread! 這行代碼來說,它分為3的步驟,包括:讀取canread值,修改canread值,寫回canread的值。 但是不要忘記了,我們的linux系統是一個多任務的系統,假如當A讀取了canread的值為1時,還沒來得及減1,就已經切換到了進程B,進程B讀出的canread的值也是1,這樣的話,B就成功打開了設備文件。如果這時再切換回了A,因為之前A已經讀出了canread為1,這時會將canread當作1來進行后續處理,也可以打開文件。這可不是我們想要的,那么有沒有其他辦法呢?答案是肯定的,下面來介紹兩種方法:
方法一:原子操作 定義:原子操作指的是在執行過程中不會被別的代碼路徑所中斷的操作。 下面是幾個常用原子操作函數: atomic_t v = ATOMIC_INIT(0); ? ? //定義原子變量v并初始化為0 atomic_read(atomic_t *v); ? ? ? ?//返回原子變量的值 void atomic_inc(atomic_t *v); ? ?//原子變量增加1 void atomic_dec(atomic_t *v); ? ?//原子變量減少1 int atomic_dec_and_test(atomic_t *v); //自減操作后測試其是否為0,為0則返回true,否則返回false。 修改代碼:
首先定義全局原子變量: static atomic_t canread= ATOMIC_INIT(1); 然后在open函數開始處加入如下代碼: ?if(!atomic_dec_and_test(&canread)) ? ? ? ? { ? ? ? ? ?? ? ? ? ? ? ? atomic_inc(&canread); ? ? ? ? ? ? return -EBUSY; ? ? ? ? }
在close函數里面加入如下代碼: atomic_inc(&canread);
方法二:信號量 定義:信號量(semaphore)是用于保護臨界區的一種常用方法,只有得到信號量的進程才能執行臨界區代碼。當獲取不到信號量時,進程進入休眠等待狀態。 下面是幾個常用的信號量相關的函數:
2. 信號量 信號量(semaphore)是用于保護臨界區的一種常用方法,只有得到信號量的進程才能執行臨界區代碼。 當獲取不到信號量時,進程進入休眠等待狀態。 定義信號量 struct semaphore sem; 初始化信號量 void sema_init (struct semaphore *sem, int val); void init_MUTEX(struct semaphore *sem);//初始化為0 static DECLARE_MUTEX(button_lock);?? ? //定義互斥鎖 獲得信號量 void down(struct semaphore * sem); int down_interruptible(struct semaphore * sem);? int down_trylock(struct semaphore * sem); 釋放信號量 void up(struct semaphore * sem); 修改代碼: 首先定義全局互斥鎖: static DECLARE_MUTEX(button_lock); 在open函數中獲得信號量: down(&button_lock); 在close函數中釋放信號量:
up(&button_lock); 我們測試時第一次運行應用程序,發現其狀態時S,第二次運行,其狀態時D,S表示睡眠狀態,是因為沒有按鍵按下而產生睡眠,而D表示一種僵死狀態,或者也可以說是一種睡眠狀態,不過它是因為沒有獲取信號量而進入僵死。那么第二個進程什么時候才能從僵死狀 態喚醒呢?那就要等到第一個進程釋放信號量。我們將第一個進程殺死,發現第二個進程的狀態變成了S,從而驗證了我們的說法。
下面我們再來談一下阻塞和非阻塞的問題: 阻塞操作:是指在執行設備操作時若不能獲得資源則掛起進程,直到滿足可操作的條件后再進行操作。被掛起的進程進入休眠狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。 非阻塞操作:進程在不能進行設備操作時并不掛起,它或者放棄,或者不停地查詢,直至可以進行操作為止。 我們在驅動程序中修改代碼: 在open函數中加入如下代碼: if (file->f_flags & O_NONBLOCK) { if (down_trylock(&button_lock)) return -EBUSY; } else { down(&button_lock); } 我們在read函數中加入如下代碼: if (filp->f_flags & O_NONBLOCK) { if (!ev_press) ?//如果沒有按鍵按下返回 return -EAGAIN; } else { wait_event_interruptible(button_waitq, ev_press);//這一句要將本來的注釋掉 } 將ev_press相關的定義添加上 添加定義:static DECLARE_WAIT_QUEUE_HEAD(button_waitq); 在中斷中補上這一句:wake_up_interruptible(&button_waitq);
測試程序如下: #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <poll.h> #include <signal.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h>
int main(int argc, char **argv) { ? ? ? ? int fd; ? ?? unsigned char key_val; ? ? ? fd = open("/dev/keys", O_RDWR); if (fd < 0) { printf("can't open!\n"); }
while (1) { ?? ?read(fd, &key_val, 1); ?? ?printf("key_val: 0x%x\n", key_val); } return 0; } 默認情況下是阻塞方式打開的,運行測試程序,當我們沒有按下按鍵時會休眠,但不會返回。 我們將: fd = open("/dev/keys", O_RDWR);改為 fd = open("/dev/keys", O_RDWR |?O_NONBLOCK); 會變成非阻塞方式打開,這時沒有按鍵按下會返回。為方便測試,我們在循環里加入:sleep(5);
總結
以上是生活随笔為你收集整理的字符设备驱动程序之按键——同步互斥阻塞的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 嵌入式linux ext4映像制作工具说
- 下一篇: e2fsprogs制作嵌入式 mkfs.