互斥实现方式(Linux)

前言:

互斥实现方式分为系统应用层与内核层,在系统应用层与内核层均有使用锁机制,在我前一篇文章中有详细介绍,这里以介绍其他方式为主,不再介绍锁,如有需要请看

互斥实现方式------锁(Linux)-CSDN博客文章浏览阅读676次,点赞14次,收藏19次。互斥机制的实现有多种方式,在应用层进程线程间的互斥通过互斥锁,无名信号量,条件变量等方式实现,在Linux内核中的并发竞态的解决通过中断屏蔽,自旋锁,信号量,互斥体,原子操作的解决办法,在本篇文章中单独将锁拿出来介绍,互斥锁以应用层介绍,自旋锁以内核底层介绍,其余方法后文中介绍。悲观锁比较基于形象化,悲观锁包括互斥锁,自旋锁,读写锁,其所实现比较悲观故称悲观锁,其锁认为多线程同时修改共享资源的概率比较高,很容易造成冲突,所以在访问共享资源前要先上锁。2.1.1互斥锁互斥锁是一种独占锁。https://blog.csdn.net/weixin_63602189/article/details/138013073?spm=1001.2014.3001.5501

一.互斥实现方式分类

 

二.应用层

2.1无名信号量

信号量是操作系统提供的一种协调共享资源访问的方法。

通常信号量表示资源的数量,对应的变量是一个整形(sem)变量

两个原子操作的系统调用函数来控制信号量的(P / V 操作):

  1. P操作:将 sem 减 1,相减后,如果 sem < 0,则进程/线程进入阻塞状态等待,否则继续,表明P操作可能会阻塞。
  2. V操作:将 sem 加 1,相加后,如果 sem <= 0,唤醒一个等待中的进程/线程,表明V操作不会阻塞。

P操作是用在进入临界区之前,V操作是用在离开临界区之后,这两个操作是必须成对出现的。

P / V 操作的函数是由操作系统管理和实现的,所以操作系统已经使得执行 P / V 函数时具有原子性。

P / V 操作的使用:

  1. 为每类共享资源设置一个信号量 s,其初始值为 1,表示临界资源未被占用。
  2. 对于进入临界区的线程来说,先对互斥信号量执行 P 操作,当线程对临界资源访问完成后再执行 V 操作,在第一个线程对信号量执行P操作时,信号量初始值减为0,表示临界资源空闲,可进行访问。
  3. 当第二个线程也来访问临界资源时,同样会对信号量先执行P操作,对临界资源访问完成后执行V操作。此时线程执行P操作时,信号量为负值表示临界资源占用,线程陷入阻塞等待。
  4. 直到第一个线程完成临界资源的访问,执行V操作将信号量置1后,此时第二个线程被唤醒进行信号量P / V操作来完成对临界资源的访问。

信号量代码实例(应用):

#include <my_head.h>

int car_num = 0;

//使用互斥锁不能解决这种同步的问题
//因为互斥锁只能保证同一时刻变量只被一个线程访问
//但是可能会出现生产者执行一次 消费者执行多次的情况

//定义无名信号量
sem_t sem;

//生产者线程的处理函数
void *func1(void *arg){
    printf("我是生产者线程 [%ld]\n", pthread_self());
    while(1){
        sleep(3);
        car_num++;
        printf("[%ld] 生产了一辆汽车 当前汽车数量 [%d]\n", pthread_self(), car_num);
        sem_post(&sem);//释放信号量
    }
}

//消费者线程的处理函数
void *func2(void *arg){
    printf("我是消费者线程 [%ld]\n", pthread_self());
    while(1){
        sem_wait(&sem);//获取信号量
        //sleep(5);//如果只使用一个无名信号量 可能会出现 消费者线程还没执行完
                //生产者线程就又执行了的情况  也就是说 生产线程可能会把信号量的值 +多
                //可以使用两个无名信号量解决这个问题 如下面的例子
        car_num--;
        printf("[%ld] 购买了一辆汽车 当前汽车数量 [%d]\n", pthread_self(), car_num);
    }
}

int main(int argc, const char *argv[]){
    sem_init(&sem, 0, 0);//初始化无名信号量
    pthread_t tid1 = 0, tid2 = 0;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid1, NULL, func1, NULL))){
        printf("pthread_create error : errno = [%d] errstr = [%s]\n", ret, strerror(ret));
        exit(EXIT_FAILURE);
    }
    if(0 != (ret = pthread_create(&tid2, NULL, func2, NULL))){
        printf("pthread_create error : errno = [%d] errstr = [%s]\n", ret, strerror(ret));
        exit(EXIT_FAILURE);
    }
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    sem_destroy(&sem);//销毁无名信号量
    return 0;
}

2.2条件变量

条件变量允许线程在等待特定条件时被阻塞,并在条件得到满足时被其他线程唤醒。

条件变量通常与互斥锁一起使用,以确保线程在检查共享资源状态时不会发生竞争条件

条件变量使用过程

  1. 获取互斥锁:线程首先需要获取与条件变量关联的互斥锁。
  2. 等待条件:线程在循环中检查某个条件是否满足,如果条件不满足,线程将调用条件变量的wait()函数,释放互斥锁,并被阻塞,直到条件得到满足。
  3. 通知唤醒:当条件满足时,另一个线程将调用释放条件变量的函数来唤醒一个或所有等待的线程。
  4. 重新获取互斥锁:被唤醒的线程将重新获取互斥锁,并继续执行。

条件变量代码实例:

#include <my_head.h>

//定义条件变量
pthread_cond_t cond;
//定义并初始化互斥锁
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

//生产者线程的处理函数
void *func1(void *arg){
    printf("我是生产者线程 [%ld]\n", pthread_self());
    while(1){
        sleep(3);
        pthread_mutex_lock(&lock);//加锁
        printf("[%ld] 生产了一个发动机\n", pthread_self());
        //释放条件变量
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);//解锁
    }
}

//消费者线程的处理函数
void *func2(void *arg){
    printf("我是消费者线程 [%ld]\n", pthread_self());
    while(1){
        pthread_mutex_lock(&lock);//加锁
        //可以先执行一些初始化的操作
        printf("[%ld] 已经涂好车漆了,等待发动机..\n", pthread_self());
        pthread_cond_wait(&cond, &lock);//获取条件变量
        printf("[%ld] 获取到发动机, 继续执行组装工作..\n", pthread_self());
        pthread_mutex_unlock(&lock);//解锁
    }
}

int main(int argc, const char *argv[]){
    pthread_cond_init(&cond, NULL);//初始化条件变量
    pthread_t tid1 = 0, tid2 = 0;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid1, NULL, func1, NULL))){
        printf("pthread_create error : errno = [%d] errstr = [%s]\n", ret, strerror(ret));
        exit(EXIT_FAILURE);
    }
    if(0 != (ret = pthread_create(&tid2, NULL, func2, NULL))){
        printf("pthread_create error : errno = [%d] errstr = [%s]\n", ret, strerror(ret));
        exit(EXIT_FAILURE);
    }
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_cond_destroy(&cond);//销毁条件变量
    pthread_mutex_destroy(&lock);//销毁互斥锁
    return 0;
}

三.内核层

3.1中断屏蔽

中断屏蔽是针对单核处理器设计的,中断屏蔽就是临时将中断关闭掉,中断屏蔽解决竞态的方法要求中断屏蔽的时间尽可能的短。如果中断屏蔽的时间很长可能会造成用户数据的丢失或者内核的崩溃。中断屏蔽保护临界区内不能执行延时,耗时甚至休眠操作

3.2信号量

信号量:当一个进程获取到信号量后,如果此时另外一个进程也想获取这个信号量,后一个进程处于休眠状态。

信号量特点

  1. 多核有效。
  2. 获取不到资源的进程不消耗CPU。
  3. 信号量不会导致死锁(CPU执行其他进程)。
  4. 信号量保护的临界区可以很大,里面可以有延时,耗时甚至休眠的操作。
  5. 信号量工作在进程上下文。
  6. 信号量不会关闭抢占。

信号量代码实例(驱动):

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define CNAME "mycdev"
#define COUNT 3
struct cdev* cdev;
int major = 510; //=0动态申请  >0静态指定
int minor = 0;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };
struct semaphore sem; //定义信号量
int mycdev_open(struct inode* inode, struct file* file)
{
    // down(&sem);
    if(down_trylock(&sem)){
        return -EBUSY;
    }
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy_to_user error\n");
        return -EIO;
    }
    return size;
}
ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy_from_user error\n");
        return -EIO;
    }
    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    up(&sem);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev_alloc error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.初始化对象
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (major == 0) {
        // 动态申请设备号
        ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
        if (ret) {
            printk("alloc_chrdev_region error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    } else if (major > 0) {
        // 静态指定
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("cdev_add error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class_create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < COUNT; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "%s%d", CNAME, i);
        if (IS_ERR(dev)) {
            printk("device_create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }

    sema_init(&sem,1); //初始化信号量
    return 0; /******************千万别忘记写了!!!***************************/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < COUNT; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

3.3互斥体

互斥体:当一个进程获取到互斥体后,如果此时另外一个进程也想获取互斥体,后一个进程处于休眠状态。

互斥体特点

  1. 多核有效。
  2. 获取不到资源的进程不消耗CPU。
  3. 互斥体不会导致死锁(CPU执行其他进程)。
  4. 互斥体可以保护的临界区很大,里面可以有延时,耗时甚至休眠操作。
  5. 互斥体工作在进程上下文。
  6. 互斥体不会关闭抢占。
  7. 互斥体在进入休眠前会适当的等一会儿,所以在进程上下文中临界区比较小的时候互斥体效率高于信号量,在不确定临界区大小的时候优先选择互斥体。

互斥体代码实例:

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define CNAME "mycdev"
#define COUNT 3
struct cdev* cdev;
int major = 510; //=0动态申请  >0静态指定
int minor = 0;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };
struct mutex lock; //定义互斥体
int mycdev_open(struct inode* inode, struct file* file)
{
    
    if(!mutex_trylock(&lock)){
        return -EBUSY;
    }
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy_to_user error\n");
        return -EIO;
    }
    return size;
}
ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy_from_user error\n");
        return -EIO;
    }
    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    mutex_unlock(&lock);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev_alloc error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.初始化对象
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (major == 0) {
        // 动态申请设备号
        ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
        if (ret) {
            printk("alloc_chrdev_region error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    } else if (major > 0) {
        // 静态指定
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("cdev_add error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class_create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < COUNT; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "%s%d", CNAME, i);
        if (IS_ERR(dev)) {
            printk("device_create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }

    mutex_init(&lock); //初始化互斥体
    return 0; /******************千万别忘记写了!!!***************************/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < COUNT; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

3.4原子操作

原子操作这种锁的特点是根据它的名字得来的,这个锁在调用的时候看成一种不可分割的整体,只用调用就一次调用完成,原子操作内部定义的是原子变量,对原子变量的值的修改是通过内联汇编完成的(省去中间环节,规避可打断的情况)

原子操作代码实例:

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define CNAME "mycdev"
#define COUNT 3
struct cdev* cdev;
int major = 510; //=0动态申请  >0静态指定
int minor = 0;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };
atomic_t atm = ATOMIC_INIT(1); //定义并初始化
int mycdev_open(struct inode* inode, struct file* file)
{
    if(!atomic_dec_and_test(&atm)){
        atomic_inc(&atm);
        return -EBUSY;
    }
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy_to_user error\n");
        return -EIO;
    }
    return size;
}
ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy_from_user error\n");
        return -EIO;
    }
    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    atomic_inc(&atm);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev_alloc error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.初始化对象
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (major == 0) {
        // 动态申请设备号
        ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
        if (ret) {
            printk("alloc_chrdev_region error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    } else if (major > 0) {
        // 静态指定
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("cdev_add error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class_create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < COUNT; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "%s%d", CNAME, i);
        if (IS_ERR(dev)) {
            printk("device_create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }

    return 0; /******************千万别忘记写了!!!***************************/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < COUNT; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

这里代码仅代表提供思路,请谨慎运行尝试!!!

注:以上所有内容均为小编所总结,图亲手所绘,如有处所还请为小编指出今早改正,与此同时也希望的总结可以提供给需要的人,谢谢!

  • 29
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晴耕雨读912

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值