嵌入式linux学习记录十三

  1. 这是 Linux 驱动开发中利用原子变量(Atomic Variable)实现"互斥锁(设备只能被一个进程打开)"的经典逻辑。

    /* 通过判断原子变量的值来检查LED 有没有被别的应用使用 */

    if (!atomic_dec_and_test(&gpioled.lock)) {

    atomic_inc(&gpioled.lock);/* 小于0 的话就加1,使其原子变量等于0 */

    return -EBUSY; /* LED 被使用,返回忙 */

    }

    /* 执行需要保护的资源 */

    /* 完成对资源操作后,把锁加1,恢复成资源可以使用的状态 */

    atomic_inc(&gpioled.lock);

  2. 这展示了在 Linux 驱动开发中,利用自旋锁(Spinlock)+ 普通变量(dev_stats)实现设备互斥访问(只允许一个进程打开)的另一种标准写法。

    spin_lock_irqsave(&gpioled.lock, flags); /* 上锁 */

    if (gpioled.dev_stats)

    { /* 如果设备被使用了 */

    spin_unlock_irqrestore(&gpioled.lock, flags); /* 解锁 */

    return -EBUSY;

    }

    gpioled.dev_stats++; /* 如果设备没有打开,那么就标记已经打开了 */

    spin_unlock_irqrestore(&gpioled.lock, flags); /* 解锁 */

    /* 对受保护资源进行操作 */

    /* 资源操作完成后,需要把普通变量(dev_stats)变成0,以恢复资源可操作性 */

    spin_lock_irqsave(&gpioled.lock, flags); /* 上锁 */

    if (gpioled.dev_stats) {

    gpioled.dev_stats--;

    }

    spin_unlock_irqrestore(&gpioled.lock, flags);/* 解锁 */

    解释:

    1. 自旋锁的角色(保镖): 它的任务非常短命。它的存在仅仅是为了保护 if (gpioled.dev_stats)dev_stats++ 这两行代码在多核并行时不被打扰。它就像一个保镖,护送进程安全地完成"检查并登记"这个动作。

    2. dev_stats 的角色(登记簿): 它的任务非常长命。一旦被写成 1,它就会一直保持为 1,直到用户层主动调用 close() 关闭设备。它负责长达几小时、甚至几天的硬件资源占有标记。

  3. 下面展示了 Linux 驱动开发中信号量(Semaphore)在实际获取锁(P 操作)时的两种不同接口:down_interruptibledown

    它们的核心区别在于:当没有信号量可用、进程被迫进入睡眠等待时,该进程是否能够响应外界的信号(如 Ctrl + C 终止信号)。

    1. down_interruptible 的逻辑

      /* 获取信号量,进入休眠状态的进程可以被信号打断 */

      if (down_interruptible(&gpioled.sem)) {

      return -ERESTARTSYS;

      }
      1.

      它的微观行为:

      1. 进程尝试获取信号量 gpioled.sem

      2. 如果此时信号量计数 count > 0(有空闲资源),进程成功拿到锁,函数返回 0,跳过 if 语句,继续往下执行。

      3. 如果此时资源被别人占用了,进程就会进入可中断的睡眠状态(TASK_INTERRUPTIBLE),让出 CPU,躺在等待队列里挂起。

      4. 为什么要用 if 判断?(信号打断机制)

        在睡觉期间,这个进程不仅在等锁释放,它还在睁着一只眼睛盯着系统信号 。如果用户突然觉得等得太久了,按下了 Ctrl + C,或者通过终端执行了 kill 命令:

        1. 内核会捕捉到这个终止信号,并强行把这个正在睡觉的进程唤醒

        2. 此时,down_interruptible 醒来后发现自己并不是因为等到了锁而醒来,而是因为被信号轰醒的

        3. 于是,它会放弃继续等锁,并返回一个非 0 值(通常是 -EINTR

        4. 代码走向: if (非0) 条件成立,进入 if 内部,执行 return -ERESTARTSYS;

          -ERESTARTSYS 的含义: 这是一个内核内部的错误码。它告诉 Linux 系统调用层:"这个进程是被信号打断的,如果可能的话,请在处理完信号后,自动重启这个系统调用;或者把错误传回给用户层(变成 EINTR 错误)。" 这样可以保证应用层程序不会卡死,能安全退场。

    down 的逻辑

    #if 0
    down(&gpioled.sem); /* 不能被信号打断 */
    #endif

    1. 如果没有资源,进程会进入不可中断的睡眠状态(TASK_UNINTERRUPTIBLE) (也就是你在 ps 命令里经常看到的 D 状态进程)。

    2. 在这个状态下,进程处于"深度昏迷"。无论你在终端里怎么按 Ctrl + C,或者执行 kill -9任何信号都会被它完全屏蔽,根本叫不醒它。

    3. 唯一的清醒方式: 必须等到持有锁的那个进程调用 up() 释放了信号量,它才能被唤醒。

    4. up(&dev->sem); /* 释放信号量,信号量值加1 */

  4. 下面展示了 Linux 内核中互斥体(Mutex)在获取锁时的两种接口:mutex_lock_interruptiblemutex_lock

    在"处理信号打断"的逻辑上,互斥体和信号量的设计哲学是完全相通的。
    1.

    mutex_lock_interruptible 的逻辑
    /* 获取互斥体,可以被信号打断 */

    if (mutex_lock_interruptible(&gpioled.lock)) {

    return -ERESTARTSYS;

    }
    1.

    复制代码
      它的行为机制:
      1. 线程尝试获取互斥锁 `gpioled.lock`。
    
      2. 如果锁当前是空闲的,线程**顺利拿到锁,函数返回 0** ,程序跳过 `if` 语句块继续往下走(进入临界区)。
    
      3. 如果锁已经被其他线程占用了,当前线程就会进入**可中断的睡眠状态(TASK_INTERRUPTIBLE)**,让出 CPU,住进这个 mutex 的等待队列里。
    1. 信号打断的处理:

      在排队睡觉期间,如果用户在终端发送了信号(比如按下了 Ctrl + C 或者是发送了 kill 信号):

      1. 内核会立刻把这个线程从睡眠中唤醒。

      2. mutex_lock_interruptible 醒来后发现自己没拿到锁,而是被信号吵醒的,于是它会放弃抢锁 ,并返回一个非 0 值 (通常是 -EINTR)。

      3. 代码进入 if 分支,执行 return -ERESTARTSYS;,让整个系统调用安全上浮退出,允许应用层程序响应这个 Ctrl + C 动作。

    2. mutex_lock 的逻辑:

      mutex_lock(&gpioled.lock); /* 不能被信号打断 */

      1. 当锁被占用时,线程会进入不可中断的睡眠状态(TASK_UNINTERRUPTIBLE) ,也就是著名的 D 状态

      2. 此时,线程对外界的任何信号(包括 kill -9)完全免疫。

      3. 它清醒的唯一条件,就是占有锁的那个线程调用 mutex_unlock() 把锁还回来。如果占有锁的线程发生死锁永远不还,这个陷入 mutex_lock 的线程就会变成一个永远无法杀掉的死进程,只能靠重启电脑解决。

信号量(Semaphore)和互斥体 (Mutex)对比。

复制代码
  #### 乐观自旋(Optimistic Spinning)

  1. **信号量(Semaphore):** 只要发现没票了,老老实实立刻去睡觉(引发上下文切换,很慢)。

  2. **互斥体(Mutex):** 没拿到锁时,它会先偷看一眼:"拿着这把锁的线程,目前是不是正在另一个 CPU 上跑着呢?"

     * 如果对方正在跑,Mutex 认为对方很快就会用完放锁,于是它会**先原地自旋(自选锁的方式)等一会**,而不是马上睡觉。

     * 如果对方已经睡觉去了,它才会跟着去睡觉。这个机制极大地减少了线程在睡眠/唤醒之间的切换开销,性能比信号量高得多。
复制代码
  #### 所有权限制(Ownership)

  1. Mutex 严格要求"谁加锁,必须由谁解锁"。

  2. 如果线程 A 调用了 `mutex_lock`,结果线程 B 试图去调用 `mutex_unlock`,内核会直接报 Bug(Kernel Panic)。而信号量(Semaphore)没有这个限制,线程 A 用 `down` 拿走票,线程 B 可以调用 `up` 还回票。
相关推荐
IAR Systems1 小时前
使用IAR Arm工具链开发和调试Zephyr RTOS
arm开发·嵌入式·iar·zephyr
fffzd2 小时前
STM32:定时器从模式
stm32·单片机·嵌入式·从模式·复位模式·门模式·触发模式
毒爪的小新2 小时前
Open WebUI 从零到一:打造属于你的私人ChatGPT
linux·ai·语言模型·chatgpt·openwebui
keyipatience2 小时前
命名管道:跨进程通信的终极指南
linux·运维·服务器
c2385610 小时前
Linux C++ 进度条进阶美化与工程化封装
linux·运维·服务器
lularible10 小时前
从沙子到车辙(7.4):《兰亭集序》的启示
开源·嵌入式·汽车电子
凡人叶枫13 小时前
Effective C++ 条款17:以独立语句将 newed 对象置入智能指针
java·linux·开发语言·c++·算法
RisunJan13 小时前
Linux命令-pgrep (通过进程名查找进程 ID)
linux·运维
信创工程师-小杨14 小时前
Linux内网环境如何解决依赖的问题
linux·运维·服务器