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");
相关文章
相关标签/搜索