linux內核信號量和互斥鎖使用

信號量概念

Linux 內核的信號量在概念和原理上與用戶態的 System V 的 IPC 機制信號量是一樣的,但是它絕不可能在內核之外使用,因此它與 System V 的 IPC 機制信號量毫不相干。
信號量在創建時需要設置一個初始值,表示同時可以有幾個任務可以訪問該信號量保護的共享資源,初始值爲 1 就變成互斥鎖(Mutex),即同時只能有一個任務可以訪問信號量保護的共享資源。
一個任務要想訪問共享資源,首先必須得到信號量,獲取信號量的操作將把信號量的值減 1,若當前信號量的值爲負數,表明無法獲得信號量,該任務必須掛起在該信號量的等待隊列等待該信號量可用;若當前信號量的值爲非負數,表示可以獲得信號量,因而可以立刻訪問被該信號量保護的共享資源。
當任務訪問完被信號量保護的共享資源後,必須釋放信號量,釋放信號量通過把信號量的值加 1 實現,如果信號量的值爲非正數,表明有任務等待當前信號量,因此它也喚醒所有等待該信號量的任務。

內核信號量核心結構

內核中使用 struct semaphore 結構來描述一個信號量,結構定義如下:

struct semaphore {
raw_spinlock_t lock;
unsigned int count; //信號值,只要這個值爲正數,則信號可用, 一般情況設置 0 或 1。
struct list_head wait_list;
};

信號量相關 API

DEFINE_SEMAPHORE (name)

宏原型 #define DEFINE_SEMAPHORE(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
宏功能 該宏聲明一個信號量 name 並初始化它的值爲 1,即聲明一個互斥鎖
宏參數 name: 要定義的信號量變量名
所在頭文件 include\linux\semaphore.h
宏定義文件 include\linux\semaphore.h
備註 Linux3.5 內核出現 在 linux 2.6 中名字爲 DECLARE_MUTEX(name)

void sema_init(struct semaphore *sem, int val)

宏(函數) 原型

void sema_init(struct semaphore *sem, int val)

宏功能 該函數用於初始化一個互斥鎖,即它把信號量 sem 的值設置爲 1。
宏參數 sem: 要初始化的信號量的指針
所在頭文件 include\linux\semaphore.h
宏定義文件 include\linux\semaphore.h
備註  

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

宏原型 void sema_init (struct semaphore *sem, int val);
宏功能 初始化設置信號量的初值,它設置信號量 sem 的值爲 val
宏參數 sem: 要初始化的信號量的指針;
val: 信號量的值
所在頭文件 include\linux\semaphore.h
宏定義文件 include\linux\semaphore.h

定義、初始化信號量相關 API:這是一個靜態定義方式。 定義一個名字爲 name,信號量值爲 1 的信號量結構變量。
示例:
struct semaphore sem;
sema_init(&sem, 1);
以上兩行等效:
DEFINE_SEMAPHORE(sem);
 

void down(struct semaphore *sem)

函數原型 void down(struct semaphore * sem);
函數功能 用於獲得信號量 sem,它會導致睡眠,因此不能在中斷上下文(包括 IRQ 上下文和 softirq 上
下文)使用該函數。該函數將把 sem 的值減 1,如果信號量 sem 的值非負,就直接返回,否
則調用者將被掛起,直到別的任務釋放該信號量才能繼續運行。
函數參數 sem: 要初始化獲取的信號量的指針;
函數返回值
所在頭文件 include\linux\semaphore.h
函數定義文件 kernel\semaphore.c

int down_timeout(struct semaphore *sem, long jiffies);
sem: 信號量結構指針
jiffies: 要設置超時時間,單位是時鐘節拍。
阻塞地請求一個信號量,如果信號量大於 0, 則可以馬上返回, 否則休眠,直到有其他進程釋放信號量
(把信號量的 count 修改爲大於 0 的值) 或超時時間到。
這個函數效果和 down 函數很像,它也是不可中斷的休眠。
 

int down_interruptible(struct semaphore * sem);

函數原型 int down_interruptible(struct semaphore * sem);
函數功能 該函數功能與 down 類似,不同之處爲, down 不會被信號(signal)打斷,但 down_interruptible
能被信號打斷,因此該函數有返回值來區分是正常返回還是被信號中斷,如果返回 0,表示
獲得信號量正常返回,如果被信號打斷,返回-EINTR。
函數參數 sem: 要獲取的信號量的指針;
函數返回值 0:得到信號量正常返回 ; 負數:被信號中斷返回
所在頭文件 include\linux\semaphore.h
函數定義文件 kernel\semaphore.c

int down_trylock(struct semaphore * sem);

函數原型 int down_trylock(struct semaphore * sem);
函數功能 該函數試着獲得信號量 sem,如果能夠立刻獲得,它就獲得該信號量並返回 0,否則,表示
不能獲得信號量 sem,返回值爲非 0 值。因此,它不會導致調用者睡眠,可以在中斷上下文
使用。
函數參數 sem: 要獲取的信號量的指針;
函數返回值 0:得到信號量正常返回 ; 非 0:得不到信號量
所在頭文件 include\linux\semaphore.h
函數定義文件 kernel\semaphore.c

void up(struct semaphore * sem);

函數原型 void up(struct semaphore * sem);
函數功能 該函數釋放信號量 sem,即把 sem 的值加 1,如果 sem 的值爲非正數,表明有任務等待該信
號量,因此喚醒這些等待者。
函數參數 sem: 要初釋放的信號量的指針;
函數返回值
頭文件 include\linux\semaphore.h
函數定義文件 kernel\semaphore.c

雖然信號量可以設置爲大於 1 的值,但是信號量在絕大部分情況下作爲互斥鎖使用。

 


簡單信號量使用例子實現設備只能被一個進程打開:

#include <linux/module.h> /* Needed by all modules */
#include <linux/init.h> /* Needed for the module-macros */
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/atomic.h>
#define LEDS_MAJOR 255
#define DEVICE_NAME "miscdev_semaphore"

DECLARE_MUTEX(lock); //定義一個互斥信號量,並初始化 1;

static int first_miscdev_open(struct inode *pinode, struct file *pfile)
{
printk (KERN_EMERG "Linux miscdevice:%s is call\r\n",__FUNCTION__);

/* 獲取信號量 */
if (!down_trylock(&lock))
return 0;
else
return -EBUSY;
}

int first_miscdev_release (struct inode *inode, struct file *file)
{
printk (KERN_EMERG "Linux miscdevice:%s is call\r\n",__FUNCTION__);

up(&lock); //釋放信號量
return 0;
}
static struct file_operations dev_fops =
{
.owner = THIS_MODULE,
.open = first_miscdev_open,
.release= first_miscdev_release,
};
static struct miscdevice misc =
{
.minor = LEDS_MAJOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
/* 模塊裝載執行函數 */
static int __init first_miscdev_init(void)
{
int ret;
ret = misc_register(&misc); //註冊混雜設備
if(ret <0)
printk (KERN_EMERG DEVICE_NAME"\t err\r\n");
printk (KERN_EMERG DEVICE_NAME"\tinitialized\n");
return ret;
}

/* 模塊卸載執行函數 */
static void __exit first_miscdev_exit(void)
{
misc_deregister(&misc);
printk(KERN_EMERG "Goodbye,cruel world!, priority = 0\n");
}
module_init(first_miscdev_init);
module_exit(first_miscdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XYD");
MODULE_DESCRIPTION("This the samlpe drv_test");

互斥信號量

互斥信號量概念:

互斥鎖主要用於實現內核中的互斥訪問功能。內核互斥鎖是在原子 API 之上實現的,但這對於內核用戶是不可見的。 在大部分場合中,對共享資源的訪問,都是使用獨佔方式, 在這種情況下, 提供專門的互斥接口給編程者使用。 對它的訪問必須遵循一些規則:同一時間只能有一個任務持有互斥鎖,而且只有這個任務可以對互斥鎖進行解鎖。互斥鎖不能進行遞歸鎖定或解鎖。一個互斥鎖對象必須通過其 API 初始化,而不能使用 memset 或複製初始化。一個任務在持有互斥鎖的時候是不能結束的。互斥鎖所使用的內存區域是不能被釋放的。使用中的互斥鎖是不能被重新初始化的。並且互斥鎖不能用於中斷上下文。但是互斥鎖比當前的內核信號量選項更快,並且更加緊湊,因此如果它們滿足您的需求,那麼它們將是您明智的選擇。

內核信號量核心結構

內核中使用 struct mutex 結構來描述一個信號量,結構定義如下:
 

struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
struct list_head wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
struct task_struct *owner;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
const char *name;
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};

關鍵成員說明:
atomic_t count:
指示互斥鎖的狀態:
1--沒有上鎖,可以獲得
0--被鎖定,不能獲得
負數--被鎖定,且可能在該鎖上有等待進程
初始化時爲沒有上鎖狀態。


spinlock_t wait_lock:
等待獲取互斥鎖中使用的自旋鎖。在獲取互斥鎖的過程中,操作會在自旋鎖的保護中進行。初始化爲爲
鎖定, 用戶一般無需關心。

struct list_head wait_list:
等待互斥鎖的進程隊列, 用戶無需關心。

互斥信號量相關 API

DEFINE_MUTEX(mutexname)

宏原型 #define DEFINE_MUTEX(mutexname) \
struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)
宏功能 該宏聲明一個互斥信號量 name 並初始化它
宏參數 name: 要定義的互斥信號量變量名
所在頭文件 include\linux\mutex.h
備註 linux3.5出現

mutex_init(mutex)

宏原型 # define mutex_init(mutex) \
do { \
static struct lock_class_key __key; \
__mutex_init((mutex), #mutex, &__key); \
} while (0)
宏功能 該宏初始化一個已經定義的互斥信號量, metex 傳遞的是指針
宏參數 mutex: 初始化的的互斥信號量指針
所在頭文件 include\linux\mutex.h
宏定義文件 include\linux\mutex.h

int mutex_is_locked(struct mutex *lock)

宏原型 int mutex_is_locked(struct mutex *lock)
宏功能 該函數檢測互斥鎖是否已經被鎖定,
宏參數 lock: 互斥鎖變量是指針
返回值 1 : 已經鎖定 0: 沒有鎖定
所在頭文件 include\linux\mutex.h

mutex_lock(lock) 或 void mutex_lock(struct mutex *lock);

宏(函數) 原型 mutex_lock(lock)

void mutex_lock(struct mutex *lock);
宏功能 該宏(函數) 獲取互斥鎖是否, 如沒有得到會休眠,直接得到爲止
宏參數 lock: 互斥鎖變量是指針
返回值
所在頭文件 include\linux\mutex.h
宏定義文件 include\linux\mutex.h
備註 根據內核配置不同,該功能有有宏版本和函數版本

mutex_lock_interruptible(lock) 或 int mutex_lock_interruptible(struct mutex *lock);

宏原型 #define mutex_lock_interruptible(lock) mutex_lock_interruptible_nested(lock, 0)

int __must_check mutex_lock_interruptible(struct mutex *lock);
宏功能 該宏(函數)獲取互斥鎖是否, 如沒有得到會休眠,直接得到爲止, 但是可以被信號中斷
宏參數 lock: 互斥鎖變量是指針
返回值 成功獲得鎖返回 0, 被信號中斷返回-EINIR
所在頭文件 include\linux\mutex.h
宏定義文件 include\linux\mutex.h
備註 根據內核配置不同,該功能有有宏版本和函數版本

int mutex_trylock(struct mutex *lock);

宏原型 int mutex_trylock(struct mutex *lock);
宏功能 該函數是獲取互斥鎖, 如沒有得到不會休眠,馬上返回
宏參數 lock: 互斥鎖變量是指針
返回值
所在頭文件 include\linux\mutex.h
宏定義文件 include\linux\mutex.c
備註 根據內核配置不同,該功能有有宏版本和函數版本

void mutex_unlock(struct mutex *lock);

宏原型 void mutex_unlock(struct mutex *lock);
宏功能 該函數是釋放互斥鎖
宏參數 lock: 互斥鎖變量是指針
返回值
所在頭文件 include\linux\mutex.h
宏定義文件 include\linux\mutex.c

互斥鎖應用例子:

/* chardev.c */
#include <linux/module.h>       /* Needed by all modules */
#include <linux/init.h>              /* Needed for the module-macros */
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>       /* 自動創建設備文件 */
#include <linux/miscdevice.h>
#include <asm/io.h>                /*ioremap*/
#include <asm/uaccess.h>       /*copy_from_user ,copy_to_user*/
#include <linux/atomic.h>
#include <linux/mutex.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>  /* s3c_gpio_cfgpin  S3C_GPIO_OUTPUT */
static struct cdev *pcdev;
static struct device *this_device = NULL;
static struct class *leds_class = NULL;

/* 是否開啓 gpio_request 函數功能 */
#define GPIO_REQUEST          1     //0 :關,非0 :開 

static int led_gpios[] = {
  EXYNOS4X12_GPM4(0),
  EXYNOS4X12_GPM4(1),
  EXYNOS4X12_GPM4(2),
  EXYNOS4X12_GPM4(3),
};

#define LED_NUM     ARRAY_SIZE(led_gpios)
#define   DEV_NAME   "myleds"
unsigned int major  = 0;                    //主設備號
unsigned int minor = 0;                   //次設備號

unsigned int devnr = 0;                    //設備號
char *chrdev_name = DEV_NAME;  //設備名
module_param(major, int, S_IRUGO | S_IWUSR);
module_param(minor, int, S_IRUGO | S_IWUSR);
module_param(chrdev_name, charp, S_IRUGO | S_IWUSR);


struct mutex lock;

//打開設備時候執行的程序
static int  chrdev_open(struct inode *pinode, struct file *pfile)
{
   mutex_lock(&lock);

  printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
  return 0;
}

//關閉設備時執行的程序
static int chrdev_release(struct inode *pinode, struct file *pfile)
{
   mutex_unlock(&lock);
  printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
  return 0;
}



//讀接口函數
static ssize_t chrdev_read ( struct file *file,
                             char __user *buf,
                             size_t count,
                             loff_t * f_pos )
{
  char led_buffer[4] = {2, 2, 2, 2};
  int i = 0;
  //  printk ( KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__ );

  /* count ==0 ,則返回0,不是錯誤 */
  if ( !count ) {
    return 0;
  }

  /* 當前已經到文件末尾,不能再寫,返回0,*/
  if ( count > LED_NUM ) {
    count =  LED_NUM;
  }


  /* 準備數據,0表示滅,1表示亮 */
  for (i = 0; i < LED_NUM; i++) {
    led_buffer[i] = !gpio_get_value(led_gpios[i]);
  }

  if ( copy_to_user ( buf, &led_buffer[0], count ) ) {
    return -EFAULT;
  }

  return count;
}



//寫接口函數
static ssize_t chrdev_write(struct file *pfile,
                            const char __user *user_buf,
                            size_t count,
                            loff_t *off)
{
  int ret = 0;
  char buf[LED_NUM] = {0};
  int i = 0;
  // printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
  //應用程序傳遞count=0下來,並不是錯誤,應該返回0
  if(count == 0) {
    return 0;
  }

  //因爲板子只有4個燈,所以防止用戶程序惡意破壞系統。
  if(count > LED_NUM) {
    count = LED_NUM;
  }

  //把用戶空間傳遞下來的數據複製到內核空間的buf數組中
  ret = copy_from_user(buf, user_buf, count); //返回,成功是0,其他是失敗
  if(ret) { //如果複製失敗返回-1;
    return -EFAULT;
  }

  for(i = 0; i < count; i++) {
    if(buf[i] == 1)
      //                  GPM4DAT &= ~(1 << (0 + i));/* 亮 */

    {
      gpio_set_value(led_gpios[i], 0);
    }

    else if(buf[i] == 0)
      //                  rGPM4DAT |=  (1 << (0 + i));/* 滅 */
    {
      gpio_set_value(led_gpios[i], 1);
    }

  }

  count  = 1;


  return count;
}



//文件操作方法:
static const  struct file_operations chrdev_fops = {
  .read      = chrdev_read,
  .write     = chrdev_write,
  .release  = chrdev_release,
  .open     = chrdev_open,
};



static int __init chrdev_init(void)
{

  int ret = 0;
  int i;

  /* IO口配置代碼 */
  for (i = 0; i < LED_NUM; i++) {
#if GPIO_REQUEST
    ret = gpio_request(led_gpios[i], "LED");
    if (ret) {
      printk("%s: request GPIO %d for LED failed, ret = %d\n",
             chrdev_name, led_gpios[i], ret);
      goto gpio_request_err;
    }
#endif

    //s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
    //gpio_set_value(led_gpios[i], 1);
    //gpio_direction_output等效於上面兩條語句,
    gpio_direction_output(led_gpios[i], 1);

  }

  //分配cdev核心結構;
  pcdev = cdev_alloc();
  if (pcdev == NULL) {
    ret = -ENOMEM;
    printk(KERN_EMERG"cdev_alloc_err error\n");
    goto cdev_alloc_err;
  }

  //如果主設備號是0,則動態分配設備號
  if(!major) {
    ret  = alloc_chrdev_region(&devnr, minor, 1, chrdev_name);
    if(ret < 0) {
      printk(KERN_EMERG"alloc_chrdev_region error\n");
      goto devnr_requst_err;
    }

  } else {

    //合成設備號
    devnr =  MKDEV(major, minor);

    //靜態註冊設備號
    ret = register_chrdev_region(devnr, 1, chrdev_name);
    if(ret != 0) {
      printk(KERN_EMERG"register_chrdev_region error\n");
      goto devnr_requst_err;
    }
  }

  //cdev結構體初始化
  cdev_init(pcdev, &chrdev_fops);

  //註冊字符設備
  ret = cdev_add(pcdev, devnr, 1);
  if ( ret < 0) {
    printk(KERN_EMERG"cdev_add error\n");
    goto cdev_add_err;
  }

  //輸出字符設備主設備號和次設備號
  printk(KERN_EMERG "name:%s,major:%d,minor:%d\n",
         chrdev_name, MAJOR(devnr), MINOR(devnr));

  //創建一個類
  leds_class =   class_create(THIS_MODULE, "leds_class");
  if ( IS_ERR(leds_class) ) {
    ret = PTR_ERR(leds_class);
    printk(KERN_EMERG"class_create error\n");
    goto class_create_err;
  }

  //創建一個設備
  this_device = device_create(leds_class, NULL, devnr, NULL, "%s", chrdev_name);
  if ( IS_ERR(this_device) ) {
    ret = PTR_ERR(this_device);
    printk(KERN_EMERG"this_device error\n");
    goto device_create_err;
  }

  mutex_init(&lock);
  return 0;

device_create_err:
  class_destroy(leds_class);
class_create_err:
  unregister_chrdev(major, chrdev_name);
cdev_add_err:
devnr_requst_err:
  if (pcdev) {
    cdev_del(pcdev);
  }
  unregister_chrdev_region(devnr, 1);
cdev_alloc_err:

#if GPIO_REQUEST
gpio_request_err:
  /* gpio 反向釋放 */
  for ( --i ; i >= 0 ; i-- ) {
    gpio_free(led_gpios[i]);
  }
#endif

  return ret;
}

static void  __exit chrdev_exit(void)
{
  int i = 0;
  device_destroy(leds_class, devnr);
  class_destroy(leds_class);

  /* gpio 反向釋放 */
  for (   ; i < 4 ; i++) {
    gpio_free(led_gpios[i]);
  }

  cdev_del(pcdev);
  unregister_chrdev_region(devnr, 1);
  printk(KERN_EMERG "Goodbye,chrdev\n");
}
module_init(chrdev_init);
module_exit(chrdev_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("learn");                //optional
MODULE_DESCRIPTION("STUDY_MODULE"); //optional


 

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