深入淺出Linux設備驅動之併發控制

深入淺出Linux設備驅動之併發控制

刺蝟@http://blog.csdn.net/littlehedgehog





注: 該系列文章轉載自arm+linux chinaunix博客圈圈主之博客——http://blog.chinaunix.net/u/22630 /article_54997.html   爲了適合我的編譯環境,源代碼有改動,但是相信我更改後的代碼更加適合現在大多數讀者的pc環境。

 
    在驅動程序中,當多個線程同時訪問相同的資源時(驅動程序中的全局變量是一種典型的共享資源),可能會引發"競態",因此我們必須對共享資源進行併發控制。Linux內核中解決併發控制的最常用方法是自旋鎖與信號量(絕大多數時候作爲互斥鎖使用)。

  自旋鎖與信號量"類似而不類",類似說的是它們功能上的相似性,"不類"指代它們在本質和實現機理上完全不一樣,不屬於一類。

  自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環查看是否該自旋鎖的保持者已經釋放了鎖,"自旋"就是"在原地打轉"。而信號量則引起調用者睡眠,它把進程從運行隊列上拖出去,除非獲得鎖。這就是它們的"不類"。[最好的理解是讀源碼 read the fucking source code!]

  但是,無論是信號量,還是自旋鎖,在任何時刻,最多隻能有一個保持者,即在任何時刻最多隻能有一個執行單元獲得鎖。這就是它們的"類似"。

  鑑於自旋鎖與信號量的上述特點,一般而言,自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用;信號量適合於保持時間較長的情況,只能在進程上下文使用。如果被保護的共享資源只在進程上下文訪問,則可以以信號量來保護該共享資源,如果對共享資源的訪問時間非常短,自旋鎖也是好的選擇。但是,如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理句柄和頂半部即軟中斷),就必須使用自旋鎖。


與信號量相關的API主要有:

  定義信號量

struct semaphore sem;

  初始化信號量

void sema_init (struct semaphore *sem, int val);

  該函數初始化信號量,並設置信號量sem的值爲val

void init_MUTEX (struct semaphore *sem);

  該函數用於初始化一個互斥鎖,即它把信號量sem的值設置爲1,等同於sema_init (struct semaphore *sem, 1);

void init_MUTEX_LOCKED (struct semaphore *sem);

  該函數也用於初始化一個互斥鎖,但它把信號量sem的值設置爲0,等同於sema_init (struct semaphore *sem, 0);

  獲得信號量

void down(struct semaphore * sem);

  該函數用於獲得信號量sem,它會導致睡眠,因此不能在中斷上下文使用;

int down_interruptible(struct semaphore * sem);

  該函數功能與down類似,不同之處爲,down不能被信號打斷,但down_interruptible能被信號打斷;

int down_trylock(struct semaphore * sem);

  該函數嘗試獲得信號量sem,如果能夠立刻獲得,它就獲得該信號量並返回0,否則,返回非0值。它不會導致調用者睡眠,可以在中斷上下文使用。

  釋放信號量

void up(struct semaphore * sem);

  該函數釋放信號量sem,喚醒等待者。

  與自旋鎖相關的API主要有:

  定義自旋鎖

spinlock_t spin;

  初始化自旋鎖

spin_lock_init(lock)

  該宏用於動態初始化自旋鎖lock

  獲得自旋鎖

spin_lock(lock)

  該宏用於獲得自旋鎖lock,如果能夠立即獲得鎖,它就馬上返回,否則,它將自旋在那裏,直到該自旋鎖的保持者釋放;

spin_trylock(lock)

  該宏嘗試獲得自旋鎖lock,如果能立即獲得鎖,它獲得鎖並返回真,否則立即返回假,實際上不再"在原地打轉";

  釋放自旋鎖

spin_unlock(lock)

  該宏釋放自旋鎖lock,它與spin_trylock或spin_lock配對使用;

   
  1. #ifndef __KERNEL__
  2. #define __KERNEL__
  3. #endif
  4. #ifndef MODULE
  5. #define MODULE
  6. #endif
  7. #include <linux/module.h>
  8. #include <linux/init.h>
  9. #include <linux/fs.h>
  10. #include <asm/uaccess.h>
  11. #include <asm/semaphore.h>
  12. MODULE_LICENSE("GPL");
  13. #define MAJOR_NUM 250 //主設備號
  14. static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);
  15. static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);
  16. static int globalvar_open(struct inode *,struct file *);
  17. static int globalvar_release(struct inode *,struct file *);
  18. //初始化字符設備驅動的file_operations結構體
  19. struct file_operations globalvar_fops =
  20. {
  21.     read:globalvar_read,
  22.     write:globalvar_write,
  23.     open:globalvar_open,
  24.     release:globalvar_release,
  25. };
  26. static int global_var = 0; //"globalvar"設備的全局變量
  27. static int global_count=0;
  28. static struct semaphore sem;
  29. static spinlock_t spin=SPIN_LOCK_UNLOCKED;
  30. static int __init globalvar_init(void)
  31. {
  32.     int ret;
  33.     //註冊設備驅動
  34.     ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
  35.     if (ret)
  36.         printk("globalvar register failure!/n");
  37.     else
  38.     {
  39.         printk("globalvar register success!/n");
  40.         init_MUTEX(&sem);
  41.     }
  42.     return ret;
  43. }
  44. static void __exit globalvar_exit(void)
  45. {
  46.     printk("globalvar unregister!/n");
  47.     unregister_chrdev(MAJOR_NUM, "globalvar");
  48. }
  49. static int globalvar_open(struct inode *inode,struct file *filep)
  50. {
  51.     spin_lock(&spin);   //這裏就限制了只能一個進程
  52.     if(global_count)
  53.     {
  54.         spin_unlock(&spin);
  55.         return -EBUSY;
  56.     }
  57.     global_count++;
  58.     spin_unlock(&spin);
  59.     return 0;
  60. }
  61. static int globalvar_release(struct inode *inode,struct file *filep)
  62. {
  63.     global_count--;
  64.     return 0;
  65. }
  66. static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
  67. {
  68.     if(down_interruptible(&sem))
  69.         return -ERESTARTSYS;
  70.     if (copy_to_user(buf, &global_var, sizeof(int)))
  71.     {
  72.         up(&sem);
  73.         return -EFAULT;
  74.     }
  75.     up(&sem);
  76.     return sizeof(int);
  77. }
  78. static ssize_t globalvar_write(struct file *filp,const char *buf,size_t len,loff_t *off)
  79. {
  80.     if(down_interruptible(&sem))
  81.         return -ERESTARTSYS;
  82. //將用戶空間的數據複製到內核空間的global_var
  83.     if (copy_from_user(&global_var, buf, sizeof(int)))
  84.     {
  85.         up(&sem);
  86.         return -EFAULT;
  87.     }
  88.     up(&sem);
  89.     return sizeof(int);
  90. }
  91. module_init(globalvar_init);
  92. module_exit(globalvar_exit);
  93. MODULE_LICENSE("GPL");
  94. MODULE_AUTHOR("neo");

爲了上述驅動程序的效果,我們啓動兩個進程分別打開/dev/globalvar。當一個進程打開/dev/globalvar後,另外一個進程將打開失敗。[注意  測試程序我全部改寫了一下,原作者的測試程序有點讓人摸不着頭腦]

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <stdio.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. //#define DEBUG 1
  7. int main()
  8. {
  9.     int fd,num,test=22;
  10.     int pid;
  11. #ifndef DEBUG
  12.     if((pid=fork())==-1)
  13.         return -1;
  14. #endif
  15.     sleep(3);   //這裏休息下 是爲了更好的模擬併發  不信  你可以註釋掉這句   很可能父子進程都會成功地獲取文件描述符 
  16.     if(!pid)
  17.         printf("I am a child /n");
  18.     else
  19.         printf("I am a parent/n");
  20.     if((fd=open("/dev/test",O_RDWR, S_IRUSR | S_IWUSR))!=-1)
  21.     {
  22.         printf("welldone!/n");
  23.         read(fd,&num,sizeof(int));
  24.         printf("the init value is %d/nnow we need to change it/n",num);
  25.         write(fd,&test,sizeof(int));
  26.         read(fd,&num,sizeof(int));
  27.         printf("%d/n",num);
  28.     }
  29.     else
  30.     {
  31.         printf("damn,we failed!/n");
  32.         perror("reason ");
  33.     }
  34.     return 0;
  35. }



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章