T11 驅動併發

1 爲何須要併發控制

​ 以T10案例代碼爲例,咱們在內核中申請了一片緩衝區,假設此刻有2個及以上進程同時來訪問這個內核驅動,並且兩者同時執行到了下述代碼區,那麼此刻內核將會拷貝2個用戶空間的數據到一個緩衝區,假設進程A恰好拷貝了一半數據到內核buffer後進程B也拷貝部分數據給內核buffer,那麼將會致使內核中的buffer內數據變得不可控。node

ret = copy_from_user(kbuf, buf, write_bytes);

​ 上述過程其實就是屬於一種競態(race condition),競態會致使對共享內存的非控制訪問,最終可能致使內存泄漏甚至系統崩潰等災難性的結果。linux

2 避免方法

  • 只要可能,就應該避免資源的共享。若是沒有併發的訪問,也就不會產生競態。所以編寫的內核代碼應該具備最少的共享,即避免使用全局變量。
  • 使用「鎖定」或者「互斥」,即保證某一進程在使用共享資源的時候其餘進程不能使用。

3 原子操做

  • 原子的操做指的是進程在執行過程當中不會被別的代碼所中斷的操做
  • 在linux中原子操做的方法有不少,有整型原子與位原子,他們在任何場景下操做都是原子的,且這些原子操做的實現都是依賴CPU來實現的,所以這些原子操做的API都是與CPU的架構密切相關

3.1 原子操做API

  • ARM架構,位於/kernel/arch/arm/include/asm/atomic.h
/*
功能:設置原子的值
*/
static inline void atomic_set(atomic_t *v, int i);

/* 定義原子變量並初始化爲0 */
atomic_t  value = ATOMIC_INIT(0);

/* 獲取原子變量的值 */
#define atomic_read(v)	((v)->counter)

/* 原子變量加減 */
#define atomic_add(i, v)	(void) atomic_add_return(i, v)
#define atomic_inc(v)		(void) atomic_add_return(1, v)
#define atomic_sub(i, v)	(void) atomic_sub_return(i, v)
#define atomic_dec(v)		(void) atomic_sub_return(1, v)

3.2 原子操做示例

  • 驅動代碼,咱們初始化atomic原子變量爲1,當第一個調用驅動的用戶代碼調用open函數的時候會將atomic變量減1,此刻爲0,此刻代表驅動正在被調用;那麼此刻當第二個用戶代碼調用驅動的open的時候會繼續將atomic變量減1,爲-1,此刻atomic_dec_and_test函數會返回0,仍表示驅動被佔用,那麼驅動將會返回一個忙的信號
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <asm/atomic.h>

#include "cdev_test_v4.h"

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    char *buffer;                           //my private memory
    int value;
    struct miscdevice *mdev;
};

/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;

static atomic_t demo_available = ATOMIC_INIT(1);

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* determine whether user has finished reading data */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;

    printk(KERN_INFO "Enter: %s\n", __func__);
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += write_bytes;
    return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    if (!atomic_dec_and_test(&demo_available)) {
        printk(KERN_ERR "file already open\n");
        /* 別忘了將信號量加回來 */
        atomic_inc(&demo_available);
        return -EBUSY;
    }
    file->private_data = (void *)demo_dev;
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    atomic_inc(&demo_available);
    return 0;
}

static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
   int ret = 0;
   struct demo_cdev *demo = file->private_data;

   switch(cmd) {
       case CDEV_TEST_V4_CLEAN:
           memset(demo->buffer, 0x00, BUF_SIZR);
           printk(KERN_INFO "cmd: clean\n");
           break;
       case CDEV_TEST_V4_GETVAL:
           put_user(demo->value, (int *)arg);
           printk(KERN_INFO "cmd: getval\n");
           break;
       case CDEV_TEST_V4_SETVAL:
           demo->value = (int)arg;
           printk(KERN_INFO "cmd: setval\n");
           break;
       default:
           break;
   } 
   return (long)ret;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
};

static struct miscdevice misc_struct = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "misc_dev",
    .fops = &demo_operation,
};

static int __init demo_init(void)
{
    int ret = -1;
    printk(KERN_INFO "Enter: %s\n", __func__);
    demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
    if (!demo_dev) {
        printk(KERN_ERR "failed to malloc demo_dev\n");
        ret = -ENOMEM;
        goto ERROR_MALLOC_DEMODEV;
    }
    demo_dev->value = 1;
    demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
    if (!demo_dev->buffer) {
        printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
        ret = -ENOMEM;
        goto ERROR_MALLOC_BUFFER;
    }
    memset(demo_dev->buffer, 0x00, BUF_SIZR);

    demo_dev->mdev = &misc_struct; 
    ret = misc_register(demo_dev->mdev);
    if (ret < 0) {
        printk(KERN_ERR "failed to register misc\n");
        goto ERROR_MISC;
    }

    printk(KERN_INFO "demo char device init done\n");
    return 0;

ERROR_MISC:
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
    kfree(demo_dev);
    demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
    return ret;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    misc_deregister(demo_dev->mdev);

    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
    kfree(demo_dev);
    demo_dev = NULL;
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
  • 測試代碼,咱們先打開一個文件,以後讓第一個進程睡眠10秒鐘,即進程佔用內核10秒鐘,以後在第一個進程沒有退出以前其餘進程都不可調用內核的open函數。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>

#include "cdev_test_v4.h"


#define DEVICE_PATH             "/dev/misc_dev"

int main(void)
{
    int fd;
    int ret;

    printf("opening...\n");
    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        printf("failed to open\n");
        return -1;
    }
    
    printf("sleeping...\n");
    sleep(7);

    printf("closing\n");
    close(fd);
    return 0;
}
  • 調試
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod atomic.ko 
[sudo] hq 的密碼: 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test 
opening...
sleeping...
closing
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test &
[1] 7064
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ opening...
sleeping...

hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
opening...
failed to open
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
opening...
failed to open
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
opening...
failed to open
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
opening...
failed to open
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ closing

[1]+  已完成               ./test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$

4 自旋鎖

自旋鎖(spin_lock)是一種典型的對臨界資源進行互斥訪問的手段,顧名思義,爲了得到一個自旋鎖,在某個CPU上運行的代碼要先執行一個原子操做,該操做測試並設置某個內存變量,在操做完成以前其餘執行單元不能訪問到這個內存變量。api

若是測試結果代表鎖已經空閒,則該程序將會得到這個自旋鎖並繼續執行;若是測試代表鎖被佔用,程序將會在一個小的循環內重複這個「測試並設置」的操做,即「自旋」的動做,即原地打轉,當自旋鎖持有者經過重置該變量釋放這個自旋鎖以後,某個等待的「測試並設置」操做向其調用者報告鎖已經被釋放。bash

4.1 自旋鎖API

/* 定義自旋鎖 */
spinlock_t lock;

/* 動態初始化自旋鎖 */
spin_lock_init(&lock);

/* 得到自旋鎖,若是可以馬上得到鎖就立刻返回,不然它將自旋在那裏直到鎖被釋放 */
spin_lock(lock);

/* 嘗試得到自旋鎖,若是可以馬上得到鎖就返回真,不然馬上返回假,即退出原地打轉的狀態 */
spin_trylock(lock);

/* 釋放自旋鎖,與lock和trylock配合使用 */
spin_unlock(lock);
  • 用法示例
spinlock_t lock;
spin_lock_init(lock);

spin_lock(lock);
...						//臨界區
spin_unlock(lock);

4.2 自旋鎖操做示例

  • 驅動代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <asm/atomic.h>
#include <linux/spinlock.h>

#include "cdev_test_v4.h"

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    char *buffer;                           //my private memory
    int value;
    struct miscdevice *mdev;
    spinlock_t lock;
    int open_count;						  //要維護的變量值
};

/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* determine whether user has finished reading data */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;

    printk(KERN_INFO "Enter: %s\n", __func__);
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += write_bytes;
    return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    spin_lock(&demo_dev->lock);
    if (demo_dev->open_count) {
        printk(KERN_ERR "already open\n");
        /* 退出的時候須要解鎖,否則會死鎖 */
        spin_unlock(&demo_dev->lock);
        return -EBUSY;
    }
    demo_dev->open_count ++;
    spin_unlock(&demo_dev->lock);
    printk(KERN_INFO "open\n");
    file->private_data = (void *)demo_dev;
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    struct demo_cdev *demo = (struct demo_cdev *)file->private_data;
    printk(KERN_INFO "Enter: %s\n", __func__);
    spin_lock(&demo->lock);
    demo->open_count --;
    spin_unlock(&demo->lock);
    return 0;
}

static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
   int ret = 0;
   struct demo_cdev *demo = file->private_data;

   switch(cmd) {
       case CDEV_TEST_V4_CLEAN:
           memset(demo->buffer, 0x00, BUF_SIZR);
           printk(KERN_INFO "cmd: clean\n");
           break;
       case CDEV_TEST_V4_GETVAL:
           put_user(demo->value, (int *)arg);
           printk(KERN_INFO "cmd: getval\n");
           break;
       case CDEV_TEST_V4_SETVAL:
           demo->value = (int)arg;
           printk(KERN_INFO "cmd: setval\n");
           break;
       default:
           break;
   } 
   return (long)ret;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
};

static struct miscdevice misc_struct = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "misc_dev",
    .fops = &demo_operation,
};

static int __init demo_init(void)
{
    int ret = -1;
    printk(KERN_INFO "Enter: %s\n", __func__);
    demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
    if (!demo_dev) {
        printk(KERN_ERR "failed to malloc demo_dev\n");
        ret = -ENOMEM;
        goto ERROR_MALLOC_DEMODEV;
    }
    demo_dev->value = 1;
    demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
    if (!demo_dev->buffer) {
        printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
        ret = -ENOMEM;
        goto ERROR_MALLOC_BUFFER;
    }
    memset(demo_dev->buffer, 0x00, BUF_SIZR);
    
    spin_lock_init(&demo_dev->lock);
    
    demo_dev->mdev = &misc_struct; 
    demo_dev->open_count = 0;

    ret = misc_register(demo_dev->mdev);
    if (ret < 0) {
        printk(KERN_ERR "failed to register misc\n");
        goto ERROR_MISC;
    }

    printk(KERN_INFO "demo char device init done\n");
    return 0;

ERROR_MISC:
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
    kfree(demo_dev);
    demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
    return ret;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    misc_deregister(demo_dev->mdev);

    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
    kfree(demo_dev);
    demo_dev = NULL;
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
  • 驅動測試代碼
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>

#include "cdev_test_v4.h"


#define DEVICE_PATH             "/dev/misc_dev"

int main(void)
{
    int fd;
    int ret;

    printf("opening...\n");
    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        printf("failed to open\n");
        return -1;
    }
    
    printf("sleeping...\n");
    sleep(7);

    printf("closing\n");
    close(fd);
    return 0;
}
  • 調試
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod spin_lock.ko 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test 
opening...
sleeping...
closing
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test &
[1] 8647
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ opening...
sleeping...

hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev 
cat: /dev/misc_dev: 設備或資源忙
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ closing

[1]+  已完成               ./test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[18503.722177] open
[18505.514911] Enter: demo_open
[18505.514912] already open
[18513.726177] Enter: demo_release

4.2 自旋鎖使用事項

  • 自旋鎖是忙等待,在得不到鎖的時候會一直「測試並設置」,這樣的話若是長時間得不到鎖就會浪費系統資源,因此適合用在等待時間比較短的狀況下,否則會下降系統性能
  • 自旋鎖可能會致使系統死鎖,當2次試圖得到這個自旋鎖的時候,CPU會死鎖
  • 自旋鎖鎖按期間不能調用可能引發進程調度的函數,若是進程得到自旋鎖以後再阻塞,如copy_from_user()copy_to_user()kmallocmsleep等函數,可能致使系統崩潰。由於當進程調度到其餘設備的時候可能不會執行解鎖操做,進而形成死鎖

5 信號量

信號量(semaphore)是用於保護臨界區的一種經常使用方法,它的用法與自旋鎖相似,可是,與自旋鎖不同的是,當獲取不到信號量時,進程不會原地打轉,而是進入休眠狀態。架構

5.1 信號量API

/* 定義信號量 */
struct semaphore sem;

/* 初始化信號量,該函數初始化信號量並設置信號量sem的值爲val,儘管信號量能夠被初始化爲大於1的值進而成爲計數信號量,可是一般不建議這麼作??? */
void sema_init(struct semaphore *sem, int val);

/* 初始化一個互斥的信號量並把sem的值設爲1 */
#define init_MUTEX(sem)	sema_init(sem, 1)
DELCEAR_MUTEX(name)

/* 初始化一個信號量並把sem的值設爲0 */
#define init_MUTEX_LOCKED(sem)	sema_init(sem, 0)
DELCEAR_MUTEX_LOCKED(name)
    
/* 獲取信號量,該函數用於獲取信號量sem,他會致使睡眠,所以不能再中斷上下文中使用,由於中斷要求快進快出,假設在中斷裏面執行down函數,那麼進程將會在中斷裏面休眠,進而使得CPU卡死在中斷裏面,沒法跳出(中斷優先級高,不可被打斷) */
void down(struct semaphore *sem);

/* 獲取信號量,由於down進入睡眠狀態的進程不能被信號打斷,可是由於down_interrruptible而進入睡眠狀態的進程能被信號打斷,信號也會致使該函數返回,返回非0。使用該函數的時候須要對返回值進行檢查,若是爲非0,一般馬上返回-ERESTARTSYS */
void down_interruptible(struct semaphore *sem);

/* 嘗試獲取信號量sem,若是能馬上得到就返回0,不然返回非0,不會致使調用者睡眠,能夠在中斷上下文中使用 */
int down_trylock(struct semaphore *sem);

/* 釋放信號量,喚醒調用者 */
void up(struct semaphore *sem);

5.2 信號量操做示例

  • 驅動代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/semaphore.h>

#include "cdev_test_v4.h"

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    char *buffer;                           //my private memory
    int value;
    struct miscdevice *mdev;
    struct semaphore lock;
};

/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* determine whether user has finished reading data */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;

    printk(KERN_INFO "Enter: %s\n", __func__);
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += write_bytes;
    return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    if (down_trylock(&demo_dev->lock)) {
        printk(KERN_INFO "already open\n");
        return -EBUSY;
    }
    file->private_data = (void *)demo_dev;
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    struct demo_cdev *demo = (struct demo_cdev*)(file->private_data);
    printk(KERN_INFO "Enter: %s\n", __func__);
    up(&demo->lock);
    return 0;
}

static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
   int ret = 0;
   struct demo_cdev *demo = file->private_data;

   switch(cmd) {
       case CDEV_TEST_V4_CLEAN:
           memset(demo->buffer, 0x00, BUF_SIZR);
           printk(KERN_INFO "cmd: clean\n");
           break;
       case CDEV_TEST_V4_GETVAL:
           put_user(demo->value, (int *)arg);
           printk(KERN_INFO "cmd: getval\n");
           break;
       case CDEV_TEST_V4_SETVAL:
           demo->value = (int)arg;
           printk(KERN_INFO "cmd: setval\n");
           break;
       default:
           break;
   } 
   return (long)ret;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
};

static struct miscdevice misc_struct = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "misc_dev",
    .fops = &demo_operation,
};

static int __init demo_init(void)
{
    int ret = -1;
    printk(KERN_INFO "Enter: %s\n", __func__);
    demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
    if (!demo_dev) {
        printk(KERN_ERR "failed to malloc demo_dev\n");
        ret = -ENOMEM;
        goto ERROR_MALLOC_DEMODEV;
    }
    demo_dev->value = 1;
    demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
    if (!demo_dev->buffer) {
        printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
        ret = -ENOMEM;
        goto ERROR_MALLOC_BUFFER;
    }
    memset(demo_dev->buffer, 0x00, BUF_SIZR);

    /* init for semaphore lock */
    sema_init(&demo_dev->lock, 1);

    demo_dev->mdev = &misc_struct; 
    ret = misc_register(demo_dev->mdev);
    if (ret < 0) {
        printk(KERN_ERR "failed to register misc\n");
        goto ERROR_MISC;
    }

    printk(KERN_INFO "demo char device init done\n");
    return 0;

ERROR_MISC:
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
    kfree(demo_dev);
    demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
    return ret;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    misc_deregister(demo_dev->mdev);

    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
    kfree(demo_dev);
    demo_dev = NULL;
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
  • 測試代碼:同自旋鎖
  • 調試
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod semaphore.ko 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chm
chmem  chmod  
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test &
[1] 9715
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ opening...
sleeping...

hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev 
cat: /dev/misc_dev: 設備或資源忙
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev 
cat: /dev/misc_dev: 設備或資源忙
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ closing

[1]+  已完成               ./test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$

5.3 信號量 VS 自旋鎖

  • 兩者都是解決互斥問題的基本手段,面對特定狀況咱們須要加以抉擇
  • 信號量是進程級的,用於多個進程之間資源的互斥,若是競爭失敗,將會發生進程上下文切換(發生休眠),由於進程上下文切換開銷較大,所以只有當前進程用資源時間較長的時候,選用信號量纔是較好的選擇
  • 而當咱們所保護的臨界資源訪問時間比較短的時候,使用自旋鎖比較方便,它不會引發進程睡眠而致使上下文切換

5.4 總結

  • 若是訪問臨界資源的時間比較長,那麼咱們能夠選擇信號量,不然使用自旋鎖
  • 信號量所保護的臨界資源可能包含可能引發阻塞的代碼,而自旋鎖則絕對要避免這樣的代碼,阻塞意味着須要進行進程上下文切換,若是進程被切換出去,這時候若是另一個進程想要獲取自旋鎖的話就會引發死鎖
  • 信號量存在於進程上下文,所以,若是被保護的資源須要在中斷或者軟中斷的狀況下使用,則只能選擇自旋鎖

6 完成量

6.1 信號量用於同步

若是咱們將信號量初始化爲0,那麼它可用於同步。同步即須要執行單元擁有某特定的執行順序,須要保證執行的前後順序。此處以以下代碼段爲例子,在執行單元A中首先將互斥量初始化爲0,故在執行down函數的時候執行單元A會休眠,直到執行單元B執行up函數釋放信號量以後,執行單元A纔會得到信號量,進而繼續執行代碼區域b。也就是說執行單元A的執行須要執行單元B的喚醒,進而實現了同步。固然linux也爲咱們提供了一個完成量來實現同步。併發

執行單元A									執行單元B
struct semphore sem;
init_MUTEX_LOCKED(&sem);/*初始化信號量爲0*/	代碼區域c
代碼區域a
down(&sem);			<------激活----------		up(&sem);
代碼區域b

6.2 完成量API

/* step1:定義完成量 */
struct completion my_completion;

/* step2:初始化完成量 */
init_completion(&my_completion);
DECLARE_COMPLETION(my_completion);

/* step3:等待完成量 */
void wait_for_completion(struct completion *c);

/* step4:喚醒完成量 */
void complete(struct completion *c);			//喚醒一個等待的執行單元
void complete_all(struct completion *c);		//喚醒全部等待的執行單元

6.3 完成量操做示例

  • 驅動代碼,此處驅動代碼在read函數處阻塞等待完成量,而在write函數處去喚醒完成量。也就是說應用層代碼在讀的時候會阻塞,一旦向內核寫入數據後才能完成讀的操做,即先寫後讀,進而達到同步狀態。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/completion.h>

#include "cdev_test_v4.h"

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    char *buffer;                           //my private memory
    int value;
    struct miscdevice *mdev;
    struct completion com;
};

/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    printk(KERN_INFO "Enter: %s\n", __func__);

    printk(KERN_INFO "Waitting for completion...\n");
    wait_for_completion(&demo->com);
    printk(KERN_INFO "Waitting done\n");

    /* determine whether user has finished reading data */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;

    printk(KERN_INFO "Enter: %s\n", __func__);
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += write_bytes;
    complete(&demo->com);
    return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    file->private_data = (void *)demo_dev;
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    return 0;
}

static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
   int ret = 0;
   struct demo_cdev *demo = file->private_data;

   switch(cmd) {
       case CDEV_TEST_V4_CLEAN:
           memset(demo->buffer, 0x00, BUF_SIZR);
           printk(KERN_INFO "cmd: clean\n");
           break;
       case CDEV_TEST_V4_GETVAL:
           put_user(demo->value, (int *)arg);
           printk(KERN_INFO "cmd: getval\n");
           break;
       case CDEV_TEST_V4_SETVAL:
           demo->value = (int)arg;
           printk(KERN_INFO "cmd: setval\n");
           break;
       default:
           break;
   } 
   return (long)ret;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
};

static struct miscdevice misc_struct = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "misc_dev",
    .fops = &demo_operation,
};

static int __init demo_init(void)
{
    int ret = -1;
    printk(KERN_INFO "Enter: %s\n", __func__);
    demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
    if (!demo_dev) {
        printk(KERN_ERR "failed to malloc demo_dev\n");
        ret = -ENOMEM;
        goto ERROR_MALLOC_DEMODEV;
    }
    demo_dev->value = 1;
    demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
    if (!demo_dev->buffer) {
        printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
        ret = -ENOMEM;
        goto ERROR_MALLOC_BUFFER;
    }
    memset(demo_dev->buffer, 0x00, BUF_SIZR);

    init_completion(&demo_dev->com);

    demo_dev->mdev = &misc_struct; 
    ret = misc_register(demo_dev->mdev);
    if (ret < 0) {
        printk(KERN_ERR "failed to register misc\n");
        goto ERROR_MISC;
    }

    printk(KERN_INFO "demo char device init done\n");
    return 0;

ERROR_MISC:
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
    kfree(demo_dev);
    demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
    return ret;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    misc_deregister(demo_dev->mdev);

    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
    kfree(demo_dev);
    demo_dev = NULL;
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
  • 調試過程,當咱們cat讀取數據的時候進程會阻塞,直到咱們向內核寫入數據後讀的線程纔會退出阻塞狀態。
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod completion.ko 
[sudo] hq 的密碼: 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev &
[1] 5602
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hanqi" -> /dev/misc_dev 
hanqi -
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[ 4463.854757] Enter: demo_init
[ 4463.854864] demo char device init done
[ 4490.500256] Enter: demo_open
[ 4490.500268] Enter: demo_read
[ 4490.500269] Waitting for completion...
[ 4546.197831] Enter: demo_open
[ 4546.197841] Enter: demo_write
[ 4546.197859] Enter: demo_release
[ 4546.197907] Waitting done
[ 4546.197989] Enter: demo_release

7 互斥鎖

雖然咱們能夠經過信號量完成互斥的操做,可是在linux中也爲咱們提供了一套標準的mutex互斥鎖機制函數

7.1 互斥鎖API

/* 定義互斥鎖 */
struct mutex my_mutex;

/* 初始化互斥鎖 */
mutex_init(&my_mutex);

/* 獲取互斥體(上鎖) */
void inline __sched mutex_lock(struct mutex *lock);
int __sched mutex_lock_interruptible(struct mutex *lock);
int __sched mutex_trylock(struct mutex *lock);

/* 釋放互斥鎖 */
void __sched mutex_unlock(struct mutex *lock);


/* 1:__sched宏展開後以下,將帶有__sched的函數放到,sched.text段,即若是不想讓函數在waiting channel中顯示出來,就應該加上__sched */
/* Attach to any functions which should be ignored in wchan output */
#define __sched 	__attribute__((__section__(".sched.text")))

7.2 wchan

  • kernel中有個waiting channel,若是用戶空間的進程休眠了,能夠查到是停在內核空間哪一個函數中等待,命令爲:cat "/proc/<pid>/wchan",進而.sched.text段的代碼是會被wchan所忽略的,schedule這個函數是不會出如今wchan的結果中
  • 舉個例子,在完成量的例子中,咱們在read函數中讓函數休眠等待完成量,此刻咱們可執行cat "/proc/<pid>/wchan"查看進程死在了哪一個函數中,顯然結果是read函數中
demo_readhq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev

demo_readhq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ps aux
hq          5664  0.0  0.0  16860   592 pts/0    D+   12:27   0:00 cat /dev/misc_dev
hq          5665  2.0  0.0  20140  3348 pts/1    R+   12:27   0:00 ps aux
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat "/proc/5664/wchan"
demo_read
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$

7.3 互斥鎖操做示例

  • 驅動代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/mutex.h>

#include "cdev_test_v4.h"

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    char *buffer;                           //my private memory
    int value;
    struct miscdevice *mdev;
    struct mutex lock;
};

/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    printk(KERN_INFO "Enter: %s\n", __func__);

    /* lock */
    mutex_lock(&demo->lock);

    /* determine whether user has finished reading data */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        mutex_unlock(&demo->lock);
        return -EFAULT;
    }
    *pos += read_bytes;

    /* unlock */
    mutex_unlock(&demo->lock);
    return read_bytes;
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;

    printk(KERN_INFO "Enter: %s\n", __func__);

    /* lock */
    mutex_lock(&demo->lock);

    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        mutex_unlock(&demo->lock);
        return -EFAULT;
    }
    *pos += write_bytes;
    
    /* unlock */
    mutex_unlock(&demo->lock);
    
    return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    file->private_data = (void *)demo_dev;
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    return 0;
}

static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
   int ret = 0;
   struct demo_cdev *demo = file->private_data;

   switch(cmd) {
       case CDEV_TEST_V4_CLEAN:
           memset(demo->buffer, 0x00, BUF_SIZR);
           printk(KERN_INFO "cmd: clean\n");
           break;
       case CDEV_TEST_V4_GETVAL:
           put_user(demo->value, (int *)arg);
           printk(KERN_INFO "cmd: getval\n");
           break;
       case CDEV_TEST_V4_SETVAL:
           demo->value = (int)arg;
           printk(KERN_INFO "cmd: setval\n");
           break;
       default:
           break;
   } 
   return (long)ret;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
};

static struct miscdevice misc_struct = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "misc_dev",
    .fops = &demo_operation,
};

static int __init demo_init(void)
{
    int ret = -1;
    printk(KERN_INFO "Enter: %s\n", __func__);
    demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
    if (!demo_dev) {
        printk(KERN_ERR "failed to malloc demo_dev\n");
        ret = -ENOMEM;
        goto ERROR_MALLOC_DEMODEV;
    }
    demo_dev->value = 1;
    demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
    if (!demo_dev->buffer) {
        printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
        ret = -ENOMEM;
        goto ERROR_MALLOC_BUFFER;
    }
    memset(demo_dev->buffer, 0x00, BUF_SIZR);

    mutex_init(&demo_dev->lock);

    demo_dev->mdev = &misc_struct; 
    ret = misc_register(demo_dev->mdev);
    if (ret < 0) {
        printk(KERN_ERR "failed to register misc\n");
        goto ERROR_MISC;
    }

    printk(KERN_INFO "demo char device init done\n");
    return 0;

ERROR_MISC:
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
    kfree(demo_dev);
    demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
    return ret;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    misc_deregister(demo_dev->mdev);

    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
    kfree(demo_dev);
    demo_dev = NULL;
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
相關文章
相關標籤/搜索