1. 程式人生 > >10.按鍵之互斥、阻塞機制

10.按鍵之互斥、阻塞機制

inter lee 多個進程 span 滿足 -1 類型 執行過程 mutex

  • 本節目標:
  1. 學習原子操作和互斥信號量,實現互斥機制,同一時刻只能一個應用程序使用驅動程序
  2. 學習阻塞和非阻塞操作

當設備被一個程序打開時,存在被另一個程序打開的可能,如果兩個或多個程序同時對設備文件進行寫操作,這就是說我們的設備資源同時被多個進程使用,對共享資源(硬件資源、和軟件上的全局變量、靜態變量等)的訪問則很容易導致競態。

顯然這不是我們想要的,所以本節引入互斥的概念:實現同一時刻,只能一個應用程序使用驅動程序

互斥其實現很簡單,就是采用一些標誌,當文件被一個進程打開後,就會設置該標誌,使其他進程無法打開設備文件。


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 阻塞測試效果

如下圖所示:

技術分享

10.按鍵之互斥、阻塞機制