解码死锁的产生与解决

死锁

死锁是多线程 / 多进程并发编程中常见的严重问题,指两个或多个线程 / 进程因互相争抢资源而陷入无限等待状态,若无外力干预将永久无法继续执行。其本质是资源分配与调度不当导致的 "资源僵局",典型现实类比是 "哲学家就餐问题":五位哲学家围坐圆桌,每人需拿起左右两根筷子才能吃饭,若所有哲学家同时拿起左侧筷子,将永远等待右侧筷子,最终全部陷入停滞。

死锁产生的四个必要条件

死锁的发生必须同时满足以下四个条件,缺一不可,这是理解和解决死锁的核心基础:

  • 资源互斥:资源在同一时间只能被一个线程 / 进程占用,无法被多个线程 / 进程同时使用(如互斥锁、打印机等硬件资源)。

  • 请求且保持:线程 / 进程已持有部分资源,又主动申请新的资源,且在申请新资源的过程中,不释放已持有的资源。

  • 不可剥夺:已被线程 / 进程占用的资源,不能被强行剥夺,只能由资源持有者主动释放。

  • 循环等待:多个线程 / 进程之间形成头尾相接的环形等待链,每个线程 / 进程都在等待下一个线程 / 进程持有的资源(如线程 A 等线程 B 的资源,线程 B 等线程 A 的资源)。

死锁的预防策略(从根源杜绝死锁)

预防死锁的核心思路是破坏上述四个必要条件中的至少一个,从设计阶段杜绝死锁发生,是最有效且常用的方案:

破坏 "请求且保持" 条件

  • 核心方案:一次性申请所有资源。线程 / 进程在启动前,需一次性申请执行过程中所需的全部资源,若无法完全满足则放弃所有申请,进入等待状态,待资源充足时再重新申请。
  • 特点:实现简单,能彻底避免 "请求且保持" 导致的死锁,但资源利用率极低,可能造成大量资源闲置。

破坏 "不可剥夺" 条件

  • 核心方案:引入资源抢占机制。若线程 / 进程持有部分资源后,申请新资源失败,需主动释放已持有的所有资源,后续需重新申请全部资源(包括之前释放的)。
  • 实操方式:结合超时机制实现,使用带超时的资源申请函数(如pthread_mutex_timedlock),超时未获取新资源时,自动释放已持有资源,避免长期占用资源导致死锁。
  • 特点:需额外处理资源释放与重新申请的逻辑,增加编程复杂度和上下文切换开销。

破坏 "循环等待" 条件(最常用、最实用)

  • 核心方案:资源有序分配法。给所有资源(如锁、设备等)定义统一的优先级或序号,所有线程 / 进程必须按照 "递增顺序" 申请资源,禁止逆序申请。
  • 原理:强制统一的申请顺序后,线程 / 进程间无法形成环形等待链。例如给锁 L1、L2、L3 编号为 1、2、3,所有线程必须先申请 L1,再申请 L2,最后申请 L3,避免 A 等 B、B 等 A 的环路。
  • 特点:实现成本低、资源利用率高,是实际开发中首选的预防方案。

死锁的避免策略(运行时动态规避)

避免死锁不提前限制资源申请方式,而是在资源分配时动态判断,避免系统进入 "不安全状态"(可能发生死锁的状态):

  • 核心算法:银行家算法。系统维护所有资源的分配状态和进程的最大资源需求,每次分配资源前,先模拟分配过程,若分配后系统仍处于安全状态(存在一条进程执行序列,可让所有进程顺利完成)则分配,否则拒绝分配。
  • 特点:资源利用率高于预防策略,但存在明显局限性 ------ 需提前预知所有进程的最大资源需求(实际开发中难以做到),且算法本身存在计算开销,仅适用于操作系统等底层系统设计,通用应用开发中极少使用。

死锁的检测与处理(事后解决方案)

该策略放任死锁发生,通过定期检测发现死锁后,采取措施打破僵局,适用于无法提前预防或避免的场景:

死锁检测

  • 核心机制:维护资源分配图。系统记录所有资源的分配关系(哪个进程持有哪个资源)和申请关系(哪个进程申请哪个资源),形成有向图,定期调用环路检测算法,若图中存在环路,则判定发生死锁。
  • 实操方式(Linux 系统):通过/proc/locks文件查看所有持有锁的进程 ID 和锁信息,命令:cat /proc/locks;也可使用ipcs -s查看 System V 信号量相关的进程资源占用。

死锁处理

  • 进程终止法:
    • 终止所有死锁进程:简单直接,但代价极高,可能导致数据丢失和任务中断。
    • 逐个终止进程:每次终止一个 "代价最小" 的进程(如优先级最低、已运行时间最短的进程),释放其资源后重新检测,直到死锁解除。
  • 资源抢占法:
    • 从死锁进程中抢占资源,分配给其他死锁进程,直到打破等待环路。需解决三个问题:选择哪个进程作为牺牲者(最小化损失)、如何回滚进程状态(抢占资源后,进程需回滚到未获取该资源的安全状态)、避免饥饿(防止同一进程反复被抢占)。

死锁场景代码示例(错误与修正)

死锁场景(错误代码)

两个线程因加锁顺序不一致,形成循环等待,最终触发死锁:

c 复制代码
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
// 定义两个全局互斥锁
pthread_mutex_t lockA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lockB = PTHREAD_MUTEX_INITIALIZER;

// 线程1:先获取lockA,再获取lockB
void *thread1(void *arg) {
    // 加锁lockA,成功则持有该锁
    pthread_mutex_lock(&lockA);
    printf("线程1:持有lockA,尝试获取lockB\n");
    // 睡眠1秒,让线程2有机会获取lockB,制造循环等待条件
    sleep(1);
    
    // 尝试获取lockB,此时线程2已持有lockB,陷入等待
    pthread_mutex_lock(&lockB); // 死锁触发点:等待线程2的lockB
    printf("线程1:成功获取lockB\n");
    
    // 解锁操作(死锁后无法执行到此处)
    pthread_mutex_unlock(&lockB);
    pthread_mutex_unlock(&lockA);
    return NULL;
}

// 线程2:先获取lockB,再获取lockA(与线程1加锁顺序相反)
void *thread2(void *arg) {
    // 加锁lockB,成功则持有该锁
    pthread_mutex_lock(&lockB);
    printf("线程2:持有lockB,尝试获取lockA\n");
    // 睡眠1秒,让线程1有机会获取lockA,制造循环等待条件
    sleep(1);
    
    // 尝试获取lockA,此时线程1已持有lockA,陷入等待
    pthread_mutex_lock(&lockA); // 死锁触发点:等待线程1的lockA
    printf("线程2:成功获取lockA\n");
    
    // 解锁操作(死锁后无法执行到此处)
    pthread_mutex_unlock(&lockA);
    pthread_mutex_unlock(&lockB);
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    // 创建线程1
    pthread_create(&tid1, NULL, thread1, NULL);
    // 创建线程2
    pthread_create(&tid2, NULL, thread2, NULL);
    
    // 等待线程结束(死锁后永久阻塞)
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

修正方案:统一加锁顺序(破坏循环等待条件)

将所有线程的加锁顺序统一为 "先 lockA 后 lockB",避免循环等待,从根源解决死锁:

c 复制代码
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t lockA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lockB = PTHREAD_MUTEX_INITIALIZER;

// 线程1:保持先lockA后lockB的顺序
void *thread1(void *arg) {
    pthread_mutex_lock(&lockA);
    printf("线程1:持有lockA,尝试获取lockB\n");
    sleep(1);
    pthread_mutex_lock(&lockB);
    printf("线程1:成功获取lockB\n");
    
    // 业务操作完成后,按加锁逆序解锁
    pthread_mutex_unlock(&lockB);
    pthread_mutex_unlock(&lockA);
    return NULL;
}

// 线程2:修改为与线程1一致的加锁顺序(先lockA后lockB)
void *thread2(void *arg) {
    pthread_mutex_lock(&lockA); // 统一先加lockA
    printf("线程2:持有lockA,尝试获取lockB\n");
    sleep(1);
    pthread_mutex_lock(&lockB); // 再加lockB
    printf("线程2:成功获取lockB\n");
    
    // 按加锁逆序解锁,避免资源泄漏
    pthread_mutex_unlock(&lockB);
    pthread_mutex_unlock(&lockA);
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

线程取消导致的死锁及解决方案

线程执行过程中被取消(如调用pthread_cancel)时,若已持有资源(如锁)且未释放,会导致其他线程永久等待该资源,引发死锁。需通过特定机制确保线程取消时释放所有资源:

方案 1:使用取消处理函数(自动释放资源)

通过pthread_cleanup_pushpthread_cleanup_pop函数,在线程被取消时自动执行资源释放操作,确保资源不泄露:

函数解析

c 复制代码
#include <pthread.h>

/**
 * 线程取消请求发送函数(POSIX标准接口)
 * @brief 向指定线程发送取消请求,请求线程终止执行,是线程间主动终止目标线程的核心接口
 * @param thread 目标线程的ID(由pthread_create创建线程时输出参数获取)
 * @return int 返回值含义:
 *              - 0:取消请求发送成功(仅表示请求已递交,不代表线程已终止);
 *              - ESRCH:目标线程ID不存在(如线程已退出、ID无效);
 *              - EINVAL:线程取消机制被禁用(目标线程通过pthread_setcancelstate设置为PTHREAD_CANCEL_DISABLE)
 * @note 取消响应特性:线程是否响应取消、何时响应,由目标线程的取消状态和类型决定,非立即终止;
 *       取消状态控制:目标线程需处于PTHREAD_CANCEL_ENABLE状态(默认启用)才能接收取消请求,禁用状态下请求会被忽略;
 *       取消类型影响:
 *           - PTHREAD_CANCEL_DEFERRED(默认):延迟响应,线程仅在执行到"取消点"时才处理取消请求(如sleep、read、pthread_join等系统调用);
 *           - PTHREAD_CANCEL_ASYNCHRONOUS:立即响应,无需等待取消点,收到请求后尽快终止(需谨慎使用,可能导致资源泄漏);
 *       资源泄漏风险:若目标线程持有锁、动态内存等资源时被取消,且未设置清理函数,会导致资源永久占用(死锁诱因之一);
 *       线程退出状态:被取消的线程退出状态为PTHREAD_CANCELED(宏定义为(void*)-1),可通过pthread_join的第二个参数获取;
 *       不可取消场景:线程执行某些关键操作(如内核态代码、原子操作)时,可能暂时无法响应取消,需等待操作完成;
 *       与pthread_exit区别:pthread_exit是线程主动终止,pthread_cancel是外部线程发起的被动终止请求
 */
int pthread_cancel(pthread_t thread);

/**
 * 线程取消清理函数压栈函数(POSIX标准接口)
 * @brief 将指定的清理函数压入当前线程的"取消清理栈",线程被取消或执行pthread_cleanup_pop(非0)时,自动执行该函数
 * @param routine 取消清理函数指针,函数原型必须为void (*routine)(void *),线程触发清理时自动调用,参数为arg
 * @param arg 传递给清理函数routine的参数(可传递锁地址、内存指针等需释放的资源标识)
 * @return 无返回值
 * @note 栈特性:清理栈遵循"后进先出(LIFO)"原则,最后压入的函数最先执行;
 *       配对使用要求:必须与pthread_cleanup_pop函数成对出现,且在同一代码块(如同一函数、同一循环体)中,否则编译报错;
 *       压栈时机:需在获取资源(如加锁、分配内存)之前压入对应清理函数,确保资源持有期间触发取消时能正确释放;
 *       执行触发条件:
 *           - 线程被pthread_cancel取消且响应成功;
 *           - 线程调用pthread_exit主动退出;
 *           - 调用pthread_cleanup_pop时参数为非0值(无论是否取消,均执行弹出的清理函数);
 *       正常执行场景:若线程未被取消且正常执行到pthread_cleanup_pop(0),则仅弹出清理函数,不执行;
 *       嵌套使用:支持多次压栈多个清理函数,适用于多资源持有场景(如同时持有多个锁,需按逆序释放)
 */
void pthread_cleanup_push(void (*routine)(void *), void *arg);

/**
 * 线程取消清理函数出栈函数(POSIX标准接口)
 * @brief 将当前线程"取消清理栈"顶部的清理函数弹出,根据参数决定是否执行该函数
 * @param execute 执行控制参数:
 *                - 0:仅弹出清理函数,不执行(适用于线程正常执行完成、无需清理资源的场景);
 *                - 非0:弹出清理函数的同时,立即执行该函数(适用于线程主动退出前、需手动触发资源清理的场景)
 * @return 无返回值
 * @note 与push的绑定关系:必须与pthread_cleanup_push一一对应,数量不匹配会导致编译链接错误(编译器通过宏检查配对);
 *       执行时机影响:参数非0时,无论线程是否被取消,都会执行弹出的清理函数,可用于主动释放资源(如函数退出前统一清理);
 *       资源释放顺序:若多次嵌套push,pop时按逆序执行(最后push的函数最先pop并执行),需与资源申请顺序相反(如先申请锁A再申请锁B,清理时先释放B再释放A);
 *       代码块限制:push和pop必须在同一作用域(如{}内),跨函数或跨条件分支的配对会导致编译失败;
 *       与线程退出的配合:线程调用pthread_exit前,若未执行pop(非0),会自动执行清理栈中所有未弹出的清理函数,确保资源释放;
 *       避免重复释放:清理函数中释放的资源(如锁、内存),需确保未被其他代码提前释放,防止双重释放导致程序崩溃
 */
void pthread_cleanup_pop(int execute)

示例

c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 互斥锁定义
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

/**
 * @brief 锁资源释放的取消处理函数
 * @param arg 传递的参数(此处为锁的地址)
 * @note 线程被取消时,该函数会自动执行,释放已持有的锁
 */
void lock_cleanup_handler(void *arg) {
    pthread_mutex_t *mutex = (pthread_mutex_t *)arg;
    printf("线程被取消,自动释放锁资源\n");
    pthread_mutex_unlock(mutex); // 释放锁,避免死锁
}

/**
 * @brief 线程执行函数
 * @param arg 线程参数(未使用)
 * @return NULL 线程退出返回值
 */
void *task_func(void *arg) {
    // 加锁前,将解锁函数压入取消处理栈
    // 第一个参数:取消时执行的处理函数地址
    // 第二个参数:传递给处理函数的参数(锁的地址)
    pthread_cleanup_push(lock_cleanup_handler, &lock);
    
    // 获取锁资源
    pthread_mutex_lock(&lock);
    printf("线程获取锁,开始执行业务逻辑\n");
    
    // 模拟耗时操作,期间可能被取消
    while (1) {
        sleep(1);
        printf("线程执行中...\n");
    }
    
    // 正常执行时,弹出取消处理函数且不执行(参数0)
    // 若线程被取消,此处不会执行,栈中的处理函数会自动调用
    pthread_cleanup_pop(0);
    return NULL;
}

int main() {
    pthread_t tid;
    // 创建线程
    pthread_create(&tid, NULL, task_func, NULL);
    
    // 主线程睡眠3秒,让子线程获取锁并执行
    sleep(3);
    // 发送线程取消请求
    printf("主线程发送取消请求\n");
    pthread_cancel(tid);
    
    // 等待线程退出,回收资源
    pthread_join(tid, NULL);
    printf("子线程已退出,资源回收完成\n");
    
    // 销毁互斥锁
    pthread_mutex_destroy(&lock);
    return 0;
}

方案 2:控制线程取消状态与类型

通过pthread_setcancelstatepthread_setcanceltype函数,控制线程对取消请求的响应方式,避免在持有资源时被取消:
函数解析

c 复制代码
#include <pthread.h>

/**
 * 设置线程取消状态(POSIX标准接口)
 * @brief 控制当前线程是否接受外部取消请求(pthread_cancel),是线程取消机制的"总开关"
 * @param state 线程新的取消状态:
 *              - PTHREAD_CANCEL_ENABLE:启用取消机制(默认值),线程可接收并响应取消请求;
 *              - PTHREAD_CANCEL_DISABLE:禁用取消机制,线程会忽略所有取消请求(请求会被挂起,直到重新启用)
 * @param oldstate 输出参数(可为NULL),用于保存线程修改前的旧取消状态,若需后续恢复旧状态可传入有效指针
 * @return int 返回值含义:
 *              - 0:状态设置成功;
 *              - EINVAL:state参数无效(既不是PTHREAD_CANCEL_ENABLE,也不是PTHREAD_CANCEL_DISABLE)
 * @note 核心作用:决定线程是否"允许被取消",与pthread_setcanceltype(取消响应方式)配合使用;
 *       禁用时的请求处理:线程禁用取消期间,外部调用pthread_cancel会返回0(请求递交成功),但线程不会响应,
 *          直到重新启用取消状态,挂起的请求才会被处理(延迟响应场景下需等待取消点);
 *       状态切换的原子性:该函数是原子操作,不会被取消请求打断,确保状态切换的一致性;
 *       与清理函数的配合:禁用取消状态时,即使线程持有资源,也不会因取消请求导致资源泄漏(因请求被忽略);
 *       适用场景:执行关键任务(如数据写入、资源初始化)时,禁用取消避免任务中断;任务完成后重新启用;
 *       线程创建默认值:新创建的线程默认取消状态为PTHREAD_CANCEL_ENABLE,无需手动启用
 */
int pthread_setcancelstate(int state, int *oldstate);

/**
 * 设置线程取消类型(POSIX标准接口)
 * @brief 当线程取消状态为启用(PTHREAD_CANCEL_ENABLE)时,指定线程响应取消请求的方式
 * @param type 线程新的取消类型:
 *              - PTHREAD_CANCEL_DEFERRED:延迟响应(默认值),线程仅在执行到"取消点"时才处理取消请求;
 *              - PTHREAD_CANCEL_ASYNCHRONOUS:立即响应,线程收到取消请求后尽快终止(无需等待取消点)
 * @param oldtype 输出参数(可为NULL),用于保存线程修改前的旧取消类型,若需后续恢复旧类型可传入有效指针
 * @return int 返回值含义:
 *              - 0:类型设置成功;
 *              - EINVAL:type参数无效(既不是PTHREAD_CANCEL_DEFERRED,也不是PTHREAD_CANCEL_ASYNCHRONOUS);
 *              - ENOTSUP:部分系统不支持PTHREAD_CANCEL_ASYNCHRONOUS类型(极少数嵌入式系统)
 * @note 依赖取消状态:仅当线程取消状态为PTHREAD_CANCEL_ENABLE时,取消类型才生效;禁用状态下类型设置无意义;
 *       取消点说明:延迟响应(DEFERRED)模式下,线程仅在执行标准定义的"取消点"时响应取消,常见取消点包括:
 *           - 系统调用:sleep、read、write、pthread_join、pthread_cond_wait、select等;
 *           - 库函数:malloc、free、printf等(部分标准库函数会触发取消点);
 *           - 无取消点场景:纯计算循环(无系统调用/库函数)中,延迟响应的线程可能永远不响应取消(需手动插入pthread_testcancel());
 *       立即响应风险:ASYNCHRONOUS类型可能导致线程在任意时刻终止(如执行临界区代码、分配内存后未初始化时),
 *          极易引发资源泄漏、数据损坏,仅适用于无资源持有、无状态的简单线程;
 *       清理函数兼容性:无论哪种类型,线程被取消时都会执行取消清理栈中的函数(pthread_cleanup_push注册的函数),
 *          但立即响应类型的清理函数执行时机更不确定,需确保清理函数是线程安全的;
 *       与pthread_testcancel配合:延迟响应模式下,若线程执行长时间纯计算(无取消点),可手动调用pthread_testcancel()
 *          插入取消点,让线程有机会响应取消请求;
 *       线程创建默认值:新创建的线程默认取消类型为PTHREAD_CANCEL_DEFERRED,推荐优先使用该类型
 */
int pthread_setcanceltype(int type, int *oldtype);
c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

/**
 * @brief 线程执行函数
 * @param arg 线程参数(未使用)
 * @return NULL 线程退出返回值
 */
void *task_func(void *arg) {
    // 设置线程取消状态为"禁用"(不接受取消请求)
    // 第一个参数:PTHREAD_CANCEL_DISABLE(禁用)/PTHREAD_CANCEL_ENABLE(启用)
    // 第二个参数:保存旧状态的指针(NULL表示不保存)
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    
    // 持有资源执行核心业务(禁用取消,避免中途被取消导致资源泄漏)
    pthread_mutex_lock(&lock);
    printf("线程获取锁,执行核心业务(禁用取消)\n");
    int count = 5;
    while (count--) {
        sleep(1);
        printf("核心业务执行中,剩余%d秒\n", count);
    }
    // 释放资源
    pthread_mutex_unlock(&lock);
    printf("核心业务完成,释放锁资源\n");
    
    // 重新启用取消请求,允许后续被取消
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    
    // 设置取消类型为"立即响应"(无需等待取消点)
    // 第一个参数:PTHREAD_CANCEL_ASYNCHRONOUS(立即响应)/PTHREAD_CANCEL_DEFERRED(延迟响应)
    // 第二个参数:保存旧类型的指针(NULL表示不保存)
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    
    // 后续循环执行,可被立即取消
    while (1) {
        sleep(1);
        printf("非核心业务执行中(允许立即取消)\n");
    }
    
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, task_func, NULL);
    
    sleep(3);
    printf("主线程发送取消请求\n");
    pthread_cancel(tid);
    
    pthread_join(tid, NULL);
    printf("子线程已退出\n");
    
    pthread_mutex_destroy(&lock);
    return 0;
}

取消点说明

线程取消请求的响应依赖 "取消点"------ 系统定义的函数调用点(如sleepreadpthread_join等,详细查看man手册第七章pthreads)。

延迟响应模式(PTHREAD_CANCEL_DEFERRED)下,线程仅在执行到取消点时才响应取消请求;

立即响应模式(PTHREAD_CANCEL_ASYNCHRONOUS)下,无需等待取消点,可随时响应取消。

死锁预防的其他实用技巧

  • 减少锁的使用范围:仅在必要的临界区使用锁,避免大面积加锁;临界区代码尽量简短,执行完成后立即解锁,减少锁持有时间。
  • 使用带超时的加锁函数 :优先使用pthread_mutex_timedlock(互斥锁超时加锁)、pthread_rwlock_timedwrlock(读写锁写超时加锁)等函数,超时未获取锁时,主动释放已持有资源并重试或退出,避免永久阻塞。
  • 避免嵌套锁:尽量不要在持有一个锁的同时,再申请另一个锁(嵌套加锁),嵌套层级越多,死锁风险越高。
  • 资源复用与池化:通过资源池(如线程池、连接池)管理资源,减少动态资源申请与释放,降低资源竞争概率。

死锁检测与处理的实操命令(Linux 系统)

  • 查看锁持有情况cat /proc/locks,列出所有进程持有的锁信息(包括进程 ID、锁类型、锁地址等)。
  • 查看 System V 信号量 / 共享内存资源ipcs -s(信号量)、ipcs -m(共享内存),查看系统中残留的资源。
  • 手动删除残留资源ipcrm -s 信号量ID(删除信号量)、ipcrm -m 共享内存ID(删除共享内存),用于清理死锁进程残留的资源。
  • 强制终止死锁进程kill -9 进程ID,通过终止死锁进程释放资源,需先通过pstop命令获取死锁进程的 ID。
相关推荐
果子没有六分钟2 小时前
【Linux】进程调度器
linux·运维·服务器
ghie90902 小时前
在Linux中设定账户密码的安全性
linux·运维·服务器
赖small强4 小时前
【Linux驱动开发】Linux SDIO 底层原理与实现细节详解
linux·驱动开发·sdio
llxxyy卢6 小时前
通关upload-labs(14-21)加分析源码
linux·运维·服务器
松涛和鸣8 小时前
11.C 语言学习:递归、宏定义、预处理、汉诺塔、Fibonacci 等
linux·c语言·开发语言·学习·算法·排序算法
C-DHEnry10 小时前
Linux 不小心挂载错磁盘导致无法启动系统咋办
linux·运维·服务器·雨云
hakukun12 小时前
Ubuntu启动时volume报错无法进入系统问题解决
linux·ubuntu
qq_4017004113 小时前
Linux磁盘配置与管理
linux·运维·服务器
hoo34313 小时前
【SolidWorks2025】3D CAD 软件:机械设计安装 + 补丁教程
linux