本節目標: 當設備被一個程式打開時,存在被另一個程式打開的可能,如果兩個或多個程式同時對設備文件進行寫操作,這就是說我們的設備資源同時被多個進程使用,對共用資源(硬體資源、和軟體上的全局變數、靜態變數等)的訪問則很容易導致競態。 顯然這不是我們想要的,所以本節引入互斥的概念:實現同一時刻,只能一個應 ...
- 本節目標:
- 學習原子操作和互斥信號量,實現互斥機制,同一時刻只能一個應用程式使用驅動程式
- 學習阻塞和非阻塞操作
當設備被一個程式打開時,存在被另一個程式打開的可能,如果兩個或多個程式同時對設備文件進行寫操作,這就是說我們的設備資源同時被多個進程使用,對共用資源(硬體資源、和軟體上的全局變數、靜態變數等)的訪問則很容易導致競態。
顯然這不是我們想要的,所以本節引入互斥的概念:實現同一時刻,只能一個應用程式使用驅動程式
互斥其實現很簡單,就是採用一些標誌,當文件被一個進程打開後,就會設置該標誌,使其他進程無法打開設備文件。
1.其中的標誌需要使用函數來操作,不能直接通過判斷變數來操作標誌
比如:
if (-- canopen != 0) //當canopen==0,表示沒有進程訪問驅動,當canopen<0:表示有進程訪問
編譯彙編來看,分了3段: 讀值、減1、判斷
如果剛好在讀值的時候發生了中斷,有另一個進程訪問時,那麼也會訪問成功,也會容易導致訪問競態。
1.1所以採用某種函數來實現,保證執行過程不被其他行為打斷,有兩種類型函數可以實現:
原子操作(像原子一樣不可再細分不可被中途打斷)
當多個進程同時訪問同一個驅動時,只能有一個進程訪問成功,其它進程會退出
互斥信號量操作
比如:A、B進程同時訪問同一個驅動時,只有A進程訪問成功了,B進程進入休眠等待狀態,當A進程執行完畢釋放後,等待狀態的B進程又來訪問,保證一個一個進程都能訪問
2. 原子操作詳解
原子操作指的是在執行過程中不會被別的代碼路徑所中斷的操作。
原子操作函數如下:
1)atomic_t v = ATOMIC_INIT(0); //定義原子變數v並初始化為0 2)atomic_read(atomic_t *v); //返回原子變數的值 3)void atomic_inc(atomic_t *v); //原子變數增加1 4)void atomic_dec(atomic_t *v); //原子變數減少1 5)int atomic_dec_and_test(atomic_t *v); //自減操作後測試其是否為0,為0則返回true,否則返回false。
2.1修改驅動程式
定義原子變數:
/*定義原子變數canopen並初始化為1 */ atomic_t canopen = ATOMIC_INIT(1);
在.open成員函數里添加:
/*自減操作後測試其是否為0,為0則返回true,否則返回false */ if(!atomic_dec_and_test(&canopen)) { atomic_inc(&canopen); //++,複位 return -1; }
在. release成員函數里添加:
atomic_inc(&canopen); //++,複位
2.2修改測試程式:
int main(int argc,char **argv) { int oflag; unsigned int val=0; fd=open("/dev/buttons",O_RDWR); if(fd<0) {printf("can't open, fd=%d\n",fd); return -1;} while(1) { read( fd, &ret, 1); //讀取驅動層數據 printf("key_vale=0X%x\r\n",ret); } return 0; }
2.3 測試效果
如下圖,可以看到第一個進程訪問驅動成功,後面的就再也不能訪問成功了
3.互斥信號量詳解
互斥信號量(semaphore)是用於保護臨界區的一種常用方法,只有得到信號量的進程才能執行臨界區代碼。
當獲取不到信號量時,進程進入休眠等待狀態。
信號量函數如下:
/*註意: 在2.6.36版本後這個函數DECLARE_MUTEX修改成DEFINE_SEMAPHORE了*/ 1)static DECLARE_MUTEX(button_lock); //定義互斥鎖button_lock,被用來後面的down和up用
2)void down(struct semaphore * sem); // 獲取不到就進入不被中斷的休眠狀態(down函數中睡眠)
3)int down_interruptible(struct semaphore * sem); //獲取不到就進入可被中斷的休眠狀態(down函數中睡眠)
4)int down_trylock(struct semaphore * sem); //試圖獲取信號量,獲取不到則立刻返回正數
5)void up(struct semaphore * sem); //釋放信號量
3.1修改驅動程式(以down函數獲取為例)
(1)定義互斥鎖變數:
/*定義互斥鎖button_lock,被用來後面的down()和up()使用 */ static DECLARE_MUTEX(button_lock);
(2)在.open成員函數里添加:
/* 獲取不到就進入不被中斷的休眠狀態(down函數中睡眠) */ down(&button_lock);
(3)在. release成員函數里添加:
/* 釋放信號量 */ up(&button_lock);
3.2修改測試程式:
int main(int argc,char **argv) { int oflag; unsigned int val=0; fd=open("/dev/buttons",O_RDWR); if(fd<0) {printf("can't open, fd=%d\n",fd); return -1;} else { printf("can open,PID=%d\n",getpid()); //打開成功,列印pid進程號 } while(1) { read( fd, &ret, 1); //讀取驅動層數據 printf("key_vale=0X%x\r\n",ret); } return 0; }
3.3 測試效果
如下圖所示,3個進程同時訪問時,只有一個進程訪問成功,其它2個進程進入休眠等待狀態
如下圖所示,多個信號量訪問時, 會一個一個進程來排序訪問
4.阻塞與非阻塞
4.1阻塞操作
進程進行設備操作時,使用down()函數,若獲取不到資源則掛起進程,將被掛起的進程進入休眠狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。
在read讀取按鍵時, 一直等待按鍵按下才返回數據
4.2非阻塞操作
進程進行設備操作時,使用down_trylock()函數,若獲取不到資源並不掛起,直接放棄。
在read讀取按鍵時, 不管有沒有數據都要返回
4.3 怎麼來判斷阻塞與非阻塞操作?
在用戶層open時,預設為阻塞操作,如果添加了” O_NONBLOCK”,表示使open()、read()、write()不被阻塞
實例:
fd=open("/dev/buttons",O_RDWR); //使用阻塞操作 fd = open("/dev/buttons ", O_RDWR | O_NONBLOCK); //使用非阻塞操作
然後在驅動設備中,通過file_operations成員函數.open、.read、.write帶的參數file->f_flags 來查看用戶層訪問時帶的參數
實例:
if( file->f_flags & O_NONBLOCK ) //非阻塞操作,獲取不到則退出 { ... ... } else //阻塞操作,獲取不到則進入休眠 { ... ... }
4.4修改應用程式,通過判斷file->f_flags來使用阻塞操作還是非阻塞操作
(1)定義互斥鎖變數:
/*定義互斥鎖button_lock,被用來後面的down()和up()使用 */ static DECLARE_MUTEX(button_lock);
(2)在.open成員函數里添加:
if( file->f_flags & O_NONBLOCK ) //非阻塞操作 { if(down_trylock(&button_lock) ) //嘗試獲取信號量,獲取不到則退出 return -1; }
else //阻塞操作 { down(&button_lock); //獲取信號量,獲取不到則進入休眠 }
(3)在. release成員函數里添加:
/*釋放信號量*/ up(&button_lock);
4.5 寫阻塞測試程式 fifth_blocktext.c
代碼如下:
int main(int argc,char **argv) { int oflag; unsigned int val=0; fd=open("/dev/buttons",O_RDWR); //使用阻塞操作 if(fd<0) {printf("can't open, fd=%d\n",fd); return -1;} else { printf("can open,PID=%d\n",getpid()); //打開成功,列印pid進程號 } while(1) { val=read( fd, &ret, 1); //讀取驅動層數據 printf("key_vale=0X%x,retrun=%d\r\n",ret,val); } return 0; }
4.6 非阻塞測試效果
如下圖所示:
4.7寫阻塞測試程式 fifth_nonblock.c
代碼如下:
int main(int argc,char **argv) { int oflag; unsigned int val=0; fd=open("/dev/buttons",O_RDWR | O_NONBLOCK); //使用非阻塞操作 if(fd<0) {printf("can't open, fd=%d\n",fd); return -1;} else { printf("can open,PID=%d\n",getpid()); //打開成功,列印pid進程號 }
while(1) { val=read( fd, &ret, 1); //讀取驅動層數據 printf("key_vale=0X%x,retrun=%d\r\n",ret,val); sleep(3); //延時3S } return 0;
}
4.8 阻塞測試效果
如下圖所示: