Linux驱动开发并发与竞争 信号量使用 互斥体(Mutex)使用

信号量

在 Linux 驱动开发中,信号量是一种重要的同步机制,用于控制对共享资源的访问,防止并发访问造成的数据不一致或竞争条件。以下是对信号量的详解,包括定义、使用方式、相关 API、示例代码及注意事项。

1. 信号量的基本概念

信号量(Semaphore) 是一个用于控制对共享资源的访问的计数器。它的基本工作原理是维护一个整数值,表示当前可用的资源数量。信号量可以分为两种类型:

  • 二元信号量(Binary Semaphore):信号量的值只能是 0 或 1,类似于互斥锁(mutex),确保同一时间只有一个进程可以访问临界区。

  • 计数信号量(Counting Semaphore):信号量的值可以大于 1,允许多个进程同时访问共享资源。

2. 信号量的工作原理

信号量的主要操作包括获取和释放。获取信号量(通常称为 “down” 操作)会减少信号量的值;释放信号量(通常称为 “up” 操作)则增加信号量的值。如果信号量的值为 0,调用获取操作的进程会被阻塞,直到信号量的值大于 0。

3. 信号量的初始化

在使用信号量之前,需要对其进行初始化。Linux 提供了两种初始化方式:静态初始化和动态初始化。

  • 静态初始化:使用 DEFINE_SEMAPHORE() 宏。

    DEFINE_SEMAPHORE(my_sem);
    
  • 动态初始化:使用 sema_init() 函数。

    struct semaphore my_sem;
    sema_init(&my_sem, 1);  // 初始值为 1,表示可用一个资源
    

4. 信号量的操作

信号量提供了几个基本操作函数,主要包括:

4.1 获取信号量
  • down():尝试获取信号量。如果信号量值大于 0,则获取成功,值减 1;如果值为 0,则进程会被阻塞。

    down(&my_sem);
    
  • down_interruptible():尝试获取信号量,可以被信号中断。如果进程在等待期间收到信号,将返回 -EINTR。

    if (down_interruptible(&my_sem)) {
        // 处理被信号中断的情况
        return -ERESTARTSYS;
    }
    
  • down_trylock():尝试获取信号量。如果信号量值为 0,则立即返回,而不会阻塞。

    if (down_trylock(&my_sem)) {
        // 无法获取信号量,处理失败
    } else {
        // 成功获取信号量
    }
    
4.2 释放信号量
  • up():释放信号量,增加信号量的值并唤醒等待信号量的进程。

    up(&my_sem);
    

5. 信号量使用示例

下面是一个简单的示例,演示如何使用信号量来保护共享资源。

示例代码:semaphore_example.c
#include 
#include 
#include 
#include 
#include 

static struct semaphore my_sem;  // 定义信号量
static int shared_resource = 0;   // 共享资源

// 修改共享资源的函数
static void modify_shared_resource(void)
{
    // 获取信号量
    if (down_interruptible(&my_sem)) {
        printk(KERN_INFO "Failed to acquire semaphore, interrupted by signal\n");
        return;
    }

    // 临界区:对共享资源进行修改
    printk(KERN_INFO "Modifying shared resource\n");
    shared_resource++;
    printk(KERN_INFO "Shared resource value: %d\n", shared_resource);

    // 模拟一些耗时操作
    mdelay(100);  // 100 毫秒的延迟

    // 释放信号量
    up(&my_sem);
}

static int __init semaphore_example_init(void)
{
    printk(KERN_INFO "Semaphore example module loaded\n");

    // 初始化信号量,初始值为 1
    sema_init(&my_sem, 1);

    // 模拟多个线程对共享资源的访问
    modify_shared_resource();
    modify_shared_resource();

    return 0;
}

static void __exit semaphore_example_exit(void)
{
    printk(KERN_INFO "Semaphore example module unloaded\n");
}

module_init(semaphore_example_init);
module_exit(semaphore_example_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example Author");
MODULE_DESCRIPTION("A simple example of semaphore usage in Linux kernel");

6. 测试步骤

  1. 编写驱动代码:将上述代码保存为 semaphore_example.c

  2. 编写 Makefile:在同一目录下创建一个 Makefile 文件:

    obj-m += semaphore_example.o
    
    all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
    clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    
  3. 编译并加载模块

    打开终端,进入代码目录,执行以下命令:

    make
    sudo insmod semaphore_example.ko
    dmesg | tail
    
  4. 卸载模块

    使用以下命令卸载模块:

    sudo rmmod semaphore_example
    dmesg | tail
    
  5. 卸载模块

    sudo rmmod semaphore_example
    

7. 输出结果

当你加载模块时,dmesg 输出的最后几行可能会如下所示:

[   10.123456] Semaphore example module loaded
[   10.123457] Modifying shared resource
[   10.123458] Shared resource value: 1
[   10.123459] Modifying shared resource
[   10.123460] Shared resource value: 2
[   10.123461] Semaphore example module unloaded

8. 输出结果解释

  • Semaphore example module loaded:模块成功加载。
  • Modifying shared resource:表示函数 modify_shared_resource() 被调用并正在修改共享资源。
  • Shared resource value: 12:显示当前共享资源的值。由于信号量的保护,两个修改操作不会发生冲突。
  • Semaphore example module unloaded:模块成功卸载。

9. 注意事项

  • 死锁:使用信号量时,要注意避免死锁。确保获取锁的顺序一致,并避免嵌套锁定多个信号量。

  • 信号中断处理:使用 down_interruptible() 时,要处理可能的信号中断情况。返回值通常为 -ERESTARTSYS,需要在用户空间重新启动系统调用。

  • 适用场景:信号量适用于较长时间的临界区和需要睡眠的情况,而自旋锁适用于较短的临界区。信号量不适合在中断上下文中使用。

通过上述内容,你可以深入了解 Linux 驱动开发中的信号量使用,帮助你更好地管理进程间对共享资源的访问。

互斥体(Mutex)

在 Linux 驱动开发中,互斥体(Mutex) 是一种常用的同步机制,用于保护共享资源,防止多个进程或线程同时访问临界区,从而避免数据不一致或竞争条件的发生。互斥体的基本原理是确保在同一时刻只有一个执行线程可以访问共享资源。

1. 互斥体的基本概念

互斥体的主要作用是提供一种简单的锁机制,确保同一时间只有一个进程能够访问某一特定资源。与信号量不同,互斥体是专门设计用于保护数据结构的,通常用于实现临界区。

2. 互斥体的基本操作

互斥体的基本操作包括获取(lock)和释放(unlock)。当一个线程获取互斥体时,其他线程必须等待直到该互斥体被释放。

  • 获取互斥体:当一个线程尝试获取一个已被其他线程锁定的互斥体时,它会被阻塞,直到该互斥体被释放。
  • 释放互斥体:当线程完成对共享资源的操作后,它应该释放互斥体,允许其他线程访问共享资源。

3. 互斥体的初始化

在使用互斥体之前,需要进行初始化。可以通过 DEFINE_MUTEX 宏进行静态初始化,或使用 mutex_init() 进行动态初始化。

  • 静态初始化

    DEFINE_MUTEX(my_mutex);
    
  • 动态初始化

    struct mutex my_mutex;
    mutex_init(&my_mutex);
    

4. 互斥体的使用

以下是互斥体的基本使用方法:

4.1 获取互斥体
mutex_lock(&my_mutex);

如果互斥体已被锁定,则调用该函数的线程会被阻塞,直到互斥体可用。

4.2 尝试获取互斥体
if (mutex_trylock(&my_mutex)) {
    // 成功获取互斥体,可以安全访问共享资源
} else {
    // 无法获取互斥体,处理失败
}

mutex_trylock() 尝试获取互斥体,如果无法获取则立即返回,不会阻塞。

4.3 释放互斥体
mutex_unlock(&my_mutex);

释放互斥体,允许其他线程访问共享资源。

5. 示例代码

以下是一个简单的 Linux 驱动模块,演示了如何使用互斥体来保护共享资源。

示例代码:mutex_example.c
#include 
#include 
#include 
#include 
#include 

static DEFINE_MUTEX(my_mutex);  // 定义互斥体
static int shared_resource = 0;  // 共享资源

// 修改共享资源的函数
static void modify_shared_resource(void)
{
    mutex_lock(&my_mutex);  // 获取互斥体

    // 临界区:对共享资源进行修改
    printk(KERN_INFO "Modifying shared resource\n");
    shared_resource++;
    printk(KERN_INFO "Shared resource value: %d\n", shared_resource);

    // 模拟一些耗时操作
    mdelay(100);  // 100 毫秒的延迟

    mutex_unlock(&my_mutex);  // 释放互斥体
}

static int __init mutex_example_init(void)
{
    printk(KERN_INFO "Mutex example module loaded\n");

    // 模拟多个线程对共享资源的访问
    modify_shared_resource();
    modify_shared_resource();

    return 0;
}

static void __exit mutex_example_exit(void)
{
    printk(KERN_INFO "Mutex example module unloaded\n");
}

module_init(mutex_example_init);
module_exit(mutex_example_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example Author");
MODULE_DESCRIPTION("A simple example of mutex usage in Linux kernel");

6. 编译和测试

  1. 编写 Makefile:创建一个名为 Makefile 的文件,内容如下:

    obj-m += mutex_example.o
    
    all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
    clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    
  2. 编译模块

    make
    
  3. 加载模块

    sudo insmod mutex_example.ko
    
  4. 查看输出

    使用以下命令查看内核日志,以查看输出结果:

    dmesg | tail -n 10
    
  5. 卸载模块

    sudo rmmod mutex_example
    

7. 预期输出结果

加载模块后,dmesg 输出的最后几行可能如下所示:

[   10.123456] Mutex example module loaded
[   10.123457] Modifying shared resource
[   10.123458] Shared resource value: 1
[   10.123459] Modifying shared resource
[   10.123460] Shared resource value: 2
[   10.123461] Mutex example module unloaded

8. 注意事项

  • 死锁:使用互斥体时,确保避免死锁,特别是在复杂的多线程环境中。
  • 不在中断上下文中使用:互斥体不应在中断上下文中使用,因为可能会导致死锁和优先级反转问题。

你可能感兴趣的:(linux,驱动开发,学习,c++,arm开发,硬件工程)