linux 同步机制之complete

本文介绍Linux内核中的completion同步机制,包括如何使用completion进行进程间的同步等待,以及通过实例展示如何在驱动程序中运用completion来确保线程正确初始化和安全退出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Linux内核中,completion是一种简单的同步机制,标志"things may proceed"。

要使用completion,必须在文件中包含<linux/completion.h>,同时创建一个类型为struct completion的变量。

这个变量可以静态地声明和初始化:
DECLARE_COMPLETION(my_comp);
或者动态初始化:
struct completion my_comp;
init_completion(&my_comp);

如果驱动程序要在执行后面操作之前等待某个过程的完成,它可以调用wait_for_completion ,以要完成的事件为参数:

void wait_for_completion(struct completion *comp);

wait_for_completion等待在completion上。如果加了interruptible,就表示线程等待可被外部发来的信号打断;如果加了killable,就表示线程只可被kill信号打断;如果加了timeout,表示等待超出一定时间会自动结束等待,timeout的单位是系统所用的时间片jiffies(多为1ms)。

如果其它部分代码可以确定事件已经完成,可以调用下面两个函数之一来唤醒等待该事件的进程:

void complete(struct completion *comp);
void complete_all(struct completion *comp); /* Linux 2.5.x以上版本 */

前一个函数将只唤醒一个等待进程,而后一个函数唤醒等待该事件的所以进程。由于completion的实现方式,即使complete在wait_for_competion之前调用,也可以正常工作。
例如,在MD设备驱动程序实现中,有一个恢复线程md_recovery_thread。驱动程序通过md_register_thread和md_unregister_thread来注册和注销恢复线程。恢复线程的执行逻辑在md_thread函数中,大致如下:

int md_thread(void * arg)
{
    线程初始化;
    while (运行) {
        处理逻辑;
        接收信号;
    }
    return 0;
}

md_register_thread将创建一个恢复线程,它必须在线程真正初始化结束之后才能返回该线程的指针。因此,其逻辑是:

mdk_thread_t *md_register_thread(void (*run) (void *), void *data, const char *name)
{
    mdk_thread_t *thread;
    ……
    struct completion event;
    /* 为线程分配空间 */
    thread = (mdk_thread_t *) kmalloc (sizeof(mdk_thread_t), GFP_KERNEL);
    ……
    init_completion(&event);
    ……
    thread->event = &event;
    /* 创建内核线程 */
    ret = kernel_thread(md_thread, thread, 0);
    /* 等待线程初始化结束 */
    ……
    wait_for_completion(&event);
    /* 返回线程指针 */
    return thread;
}


而md_unregister_thread通过向线程发送SIGKILL信号注销恢复线程,它也需要在线程真正退出后才能释放线程所占用的内存。因此,其逻辑是:

void md_unregister_thread(mdk_thread_t *thread)
{
    struct completion event;
    init_completion(&event);
    thread->event = &event;
    ……
    /* 向线程发送SIGKILL信号终止其运行 */
    md_interrupt_thread(thread);
    /* 等待线程退出 */
    wait_for_completion(&event);
    /* 释放线程所占用的内存 */
    kfree(thread);
}

如果考虑completion,md_thread的逻辑是:

int md_thread(void * arg)
{
    线程初始化;
    complete(thread->event); 
    while (运行) {
        处理逻辑;
        接收信号;
    }
    complete(thread->event); 
    return 0;
}

需要说明的是,由于等待事件是在驱动程序和恢复线程中的一个共享资源,它必须是一个全局变量,或者如实现代码中,定义为一个局部变量,而将其指针放在恢复线程结构中。
typedef struct mdk_thread_s {
    ……
    struct completion *event;
    ……
} mdk_thread_t;

### Linux 操作系统中进程的创建 在Linux操作系统中,新进程可以通过`fork()`系统调用来创建。当父进程调用此函数时,会复制当前进程环境并生成一个新的子进程。这个新的子进程中几乎包含了与父进程相同的所有属性,除了PID(进程ID)、PPID(父进程ID)和其他一些特定于实例的数据结构[^5]。 对于线程级别的轻量级进程,则通常使用`clone()`来实现更细粒度的任务划分。这种方式允许开发者指定哪些资源应该被共享以及如何设置新线程的行为参数。 ```c pid_t pid; pid = fork(); if (pid < 0) { perror("Fork failed"); } else if (pid == 0) { printf("This is child process\n"); } else { wait(NULL); printf("Child complete.\n"); } ``` ### 进程调度机制 Linux内核中的调度器采用了完全公平队列算法(CFS),它不同于早期版本提到的协同式多任务处理模式。CFS旨在模拟理想的单轮循环调度策略,在其中每个可运行实体都获得相等的时间片来进行CPU运算。这使得即使存在大量活跃进程的情况下也能保持良好的响应性能和效率[^1]。 此外,针对不同应用场景需求,Linux也提供了多种实时调度类别的支持,比如SCHED_FIFO(先入先出) 和 SCHED_RR(时间片轮转), 它们能够满足对延迟敏感型应用的要求。 ### 进程同步方法 为了确保多个并发执行的进程能有序访问临界区内的公共资源而不发生冲突,Linux实现了若干种有效的同步手段: - **信号量(Semaphore)**:提供了一套原子操作接口用于控制进入某段代码区域前后的计数值变化; - **读写锁(RWLocks)** :适用于有较多只读请求场景下的竞争状况缓解; - **条件变量(Condition Variables)** : 结合互斥体一起工作, 可以让某个或某些线程等待直到另一些事件的发生才继续往下走; 这些工具共同构成了复杂环境下可靠的协作框架[^3]. ### 进程间通信(IPC) Linux 支持丰富的IPC设施供应用程序之间交换信息: - **管道(Pipes)/命名管道(FIFOs)** – 单向流导向连接两个关联紧密的过程单元,默认情况下是非阻塞式的; - **消息队列(Message Queues)**– 存储由发送者放入的消息对象直至接收方取回为止的一种缓冲池形式; - **共享内存(Shared Memory Segments)** - 让不同的地址空间映射到同一物理页面上从而达到高效传递大数据的目的; 以上每一种方式都有其特点和适用范围,并且可以根据实际需要灵活组合运用[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值