柳州市纤凝网

ARMLinux驱动开发篇---Linux并发与竞争实验(信号量实现LED设备互斥访问)

2026-03-28 15:52:02 浏览次数:1
详细信息

ARMLinux驱动开发:信号量实现LED设备互斥访问

一、实验概述

本实验通过信号量机制实现ARM Linux驱动中的并发控制,确保LED设备在多个进程同时访问时的互斥性,避免竞争条件。

二、关键概念

1. 并发与竞争问题

2. 信号量机制

#include <linux/semaphore.h>

// 信号量结构体
struct semaphore {
    raw_spinlock_t      lock;
    unsigned int        count;
    struct list_head    wait_list;
};

// 常用API
void sema_init(struct semaphore *sem, int val);  // 初始化
void down(struct semaphore *sem);                // 获取信号量(可休眠)
int down_trylock(struct semaphore *sem);         // 尝试获取(非阻塞)
void up(struct semaphore *sem);                   // 释放信号量

三、实验驱动代码实现

1. 驱动框架设计

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define DEVICE_NAME "led_mutex"
#define LED_NUM     4
#define CLASS_NAME  "led_class"

// LED设备结构体
struct led_device {
    dev_t devno;
    struct cdev cdev;
    struct class *class;
    struct device *device;

    // 硬件相关
    void __iomem *gpio_base;
    unsigned int gpio_pins[LED_NUM];

    // 信号量保护
    struct semaphore led_sem;

    // 当前状态
    unsigned long led_state;
};

static struct led_device *led_dev;

2. 信号量初始化与使用

// 初始化信号量
static int led_semaphore_init(void)
{
    // 初始化互斥信号量(初始值为1)
    sema_init(&led_dev->led_sem, 1);

    // 或者使用互斥信号量宏
    // DEFINE_SEMAPHORE(led_dev->led_sem);  // 静态定义

    return 0;
}

// 带信号量保护的LED控制函数
static void set_led_state(int led_num, int state)
{
    unsigned long flags;
    unsigned int reg_val;

    // 获取信号量(进入临界区)
    if (down_interruptible(&led_dev->led_sem)) {
        printk(KERN_ERR "Failed to get semaphore, interrupted\n");
        return -ERESTARTSYS;
    }

    // 临界区开始
    spin_lock_irqsave(&led_dev->hw_lock, flags);

    // 读取当前GPIO状态
    reg_val = readl(led_dev->gpio_base + GPIO_DATA_REG);

    if (state) {
        // 点亮LED
        reg_val |= (1 << led_dev->gpio_pins[led_num]);
        led_dev->led_state |= (1 << led_num);
    } else {
        // 熄灭LED
        reg_val &= ~(1 << led_dev->gpio_pins[led_num]);
        led_dev->led_state &= ~(1 << led_num);
    }

    // 写回寄存器
    writel(reg_val, led_dev->gpio_base + GPIO_DATA_REG);

    spin_unlock_irqrestore(&led_dev->hw_lock, flags);
    // 临界区结束

    // 释放信号量
    up(&led_dev->led_sem);

    printk(KERN_INFO "LED %d set to %s\n", led_num, state ? "ON" : "OFF");
}

3. 文件操作实现

static ssize_t led_write(struct file *filp, const char __user *buf,
                        size_t count, loff_t *ppos)
{
    int ret;
    struct led_cmd cmd;

    if (count != sizeof(struct led_cmd))
        return -EINVAL;

    // 从用户空间拷贝命令
    if (copy_from_user(&cmd, buf, sizeof(struct led_cmd)))
        return -EFAULT;

    // 验证参数
    if (cmd.led_num >= LED_NUM || cmd.state > 1)
        return -EINVAL;

    // 获取信号量
    if (down_trylock(&led_dev->led_sem)) {
        // 非阻塞方式,如果获取失败立即返回
        return -EBUSY;
    }

    // 临界区:控制LED
    ret = control_led_hardware(cmd.led_num, cmd.state);

    // 释放信号量
    up(&led_dev->led_sem);

    return ret ? ret : count;
}

static ssize_t led_read(struct file *filp, char __user *buf,
                       size_t count, loff_t *ppos)
{
    unsigned long state;

    if (*ppos > 0)
        return 0;

    // 获取信号量(可中断)
    if (down_interruptible(&led_dev->led_sem)) {
        return -ERESTARTSYS;
    }

    // 读取LED状态
    state = led_dev->led_state;

    // 释放信号量
    up(&led_dev->led_sem);

    // 拷贝到用户空间
    if (copy_to_user(buf, &state, sizeof(state)))
        return -EFAULT;

    *ppos = sizeof(state);
    return sizeof(state);
}

4. 完整的驱动初始化

static int __init led_mutex_init(void)
{
    int ret;
    int i;

    // 1. 分配设备结构
    led_dev = kzalloc(sizeof(struct led_device), GFP_KERNEL);
    if (!led_dev)
        return -ENOMEM;

    // 2. 申请设备号
    ret = alloc_chrdev_region(&led_dev->devno, 0, 1, DEVICE_NAME);
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate device number\n");
        goto fail_alloc;
    }

    // 3. 初始化信号量
    sema_init(&led_dev->led_sem, 1);  // 初始值为1(互斥信号量)

    // 4. 初始化cdev
    cdev_init(&led_dev->cdev, &led_fops);
    led_dev->cdev.owner = THIS_MODULE;

    // 5. 添加cdev到系统
    ret = cdev_add(&led_dev->cdev, led_dev->devno, 1);
    if (ret < 0) {
        printk(KERN_ERR "Failed to add cdev\n");
        goto fail_cdev;
    }

    // 6. 创建设备类
    led_dev->class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(led_dev->class)) {
        ret = PTR_ERR(led_dev->class);
        goto fail_class;
    }

    // 7. 创建设备节点
    led_dev->device = device_create(led_dev->class, NULL,
                                   led_dev->devno, NULL, DEVICE_NAME);
    if (IS_ERR(led_dev->device)) {
        ret = PTR_ERR(led_dev->device);
        goto fail_device;
    }

    // 8. 映射硬件寄存器(示例)
    led_dev->gpio_base = ioremap(GPIO_BASE_ADDR, SZ_4K);
    if (!led_dev->gpio_base) {
        ret = -ENOMEM;
        goto fail_ioremap;
    }

    // 9. 配置GPIO引脚
    for (i = 0; i < LED_NUM; i++) {
        configure_gpio_as_output(led_dev->gpio_base, led_dev->gpio_pins[i]);
    }

    // 10. 初始化硬件锁
    spin_lock_init(&led_dev->hw_lock);

    printk(KERN_INFO "LED mutex driver loaded successfully\n");
    return 0;

    // 错误处理(略)
    ...
}

四、测试程序

1. 并发测试程序

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

#define DEV_PATH "/dev/led_mutex"

struct led_cmd {
    int led_num;
    int state;
};

void *thread_func(void *arg)
{
    int fd;
    struct led_cmd cmd;
    int thread_id = *(int *)arg;

    fd = open(DEV_PATH, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        pthread_exit(NULL);
    }

    // 随机控制LED
    srand(time(NULL) + thread_id);

    for (int i = 0; i < 10; i++) {
        cmd.led_num = rand() % 4;
        cmd.state = rand() % 2;

        printf("Thread %d: Setting LED %d to %d\n",
               thread_id, cmd.led_num, cmd.state);

        if (write(fd, &cmd, sizeof(cmd)) < 0) {
            if (errno == EBUSY) {
                printf("Thread %d: Device busy, retrying...\n", thread_id);
                usleep(1000);  // 等待1ms后重试
                continue;
            }
            perror("Write failed");
        }

        usleep(rand() % 10000);  // 随机延迟
    }

    close(fd);
    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
    pthread_t threads[5];
    int thread_ids[5];
    int i;

    // 创建5个并发线程
    for (i = 0; i < 5; i++) {
        thread_ids[i] = i;
        if (pthread_create(&threads[i], NULL, thread_func, &thread_ids[i])) {
            perror("Failed to create thread");
            return 1;
        }
    }

    // 等待所有线程完成
    for (i = 0; i < 5; i++) {
        pthread_join(threads[i], NULL);
    }

    printf("All threads completed successfully\n");
    return 0;
}

2. 测试脚本

#!/bin/bash
# test_led_mutex.sh

echo "Loading LED mutex driver..."
sudo insmod led_mutex.ko

echo "Testing with single process..."
sudo ./test_single_process

echo "Testing with multiple processes..."
sudo ./test_concurrent_processes

echo "Testing concurrent threads..."
sudo ./test_concurrent_threads

echo "Checking kernel messages..."
dmesg | tail -20

echo "Cleaning up..."
sudo rmmod led_mutex

五、信号量使用注意事项

1. 信号量选择策略

// 1. 互斥访问(推荐)
sema_init(&sem, 1);          // 初始值为1的互斥信号量

// 2. 允许多个读者
sema_init(&sem, 5);          // 允许最多5个并发访问

// 3. 信号量获取方式
down(&sem);                  // 不可中断(可能造成死锁)
down_interruptible(&sem);    // 可被信号中断(推荐)
down_trylock(&sem);          // 非阻塞,立即返回
down_timeout(&sem, HZ);      // 带超时等待

2. 避免常见问题

// 问题1:信号量未配对释放
void buggy_function(void)
{
    down(&sem);
    if (error_condition) {
        return;  // 错误!未释放信号量
    }
    up(&sem);
}

// 正确做法:使用goto或提前释放
void correct_function(void)
{
    down(&sem);

    if (error_condition) {
        up(&sem);  // 提前释放
        return;
    }

    // 正常处理
    up(&sem);
}

// 问题2:嵌套获取
void nested_access(void)
{
    down(&sem);
    another_function();  // 内部可能再次获取同一个信号量
    up(&sem);
}

// 解决方案:使用可重入锁(mutex)或避免嵌套

六、性能优化建议

1. 读写信号量

#include <linux/rwsem.h>

// 读写信号量允许多个读者或一个写者
struct rw_semaphore led_rwsem;

// 初始化
init_rwsem(&led_rwsem);

// 读者
down_read(&led_rwsem);
// 读操作...
up_read(&led_rwsem);

// 写者
down_write(&led_rwsem);
// 写操作...
up_write(&led_rwsem);

2. 完成量机制

#include <linux/completion.h>

// 用于线程间同步
struct completion led_ready;
init_completion(&led_ready);

// 等待完成
wait_for_completion(&led_ready);

// 完成事件
complete(&led_ready);

七、调试技巧

1. 调试输出

// 添加调试信息
#define DEBUG_SEMAPHORE 1

#ifdef DEBUG_SEMAPHORE
#define sem_debug(fmt, args...) \
    printk(KERN_DEBUG "LED_SEM: " fmt, ##args)
#else
#define sem_debug(fmt, args...) 
#endif

// 在关键位置添加调试
down_interruptible(&led_dev->led_sem);
sem_debug("Semaphore acquired by process %d\n", current->pid);

up(&led_dev->led_sem);
sem_debug("Semaphore released by process %d\n", current->pid);

2. 死锁检测

// 使用调试工具检测死锁
// 1. 打开内核死锁检测
CONFIG_DEBUG_LOCK_ALLOC=y
CONFIG_PROVE_LOCKING=y

// 2. 使用lockdep工具
#include <linux/lockdep.h>

// 声明锁类
static struct lock_class_key led_lock_key;

// 初始化时注册
lockdep_set_class(&led_dev->led_sem.lock, &led_lock_key);

八、实验总结

通过本实验,我们实现了:

信号量初始化:使用sema_init()初始化互斥信号量 临界区保护:使用down()/up()保护LED硬件访问 并发控制:支持多进程/多线程安全访问 错误处理:正确处理信号量获取失败情况 资源清理:确保模块卸载时释放所有资源

关键知识点:

扩展思考:

如何优化信号量性能? 何时使用自旋锁替代信号量? 如何处理优先级反转问题? 如何实现读写信号量优化?

这个实验为理解Linux内核并发控制机制提供了实践基础,是设备驱动开发的重要技能。

相关推荐