Linux内核API wait_for_completion

wait_for_completion函数功能描述:此函数用于阻塞当前进程,等待其他进程的执行结束,被等待进程保存在输入参数的wait字段所代表的等待队列中,只有当等待队列中的进程被函数complete( )complete_all( )唤醒之后,等待才有可能结束,当前的进程才能够继续执行,否则会一直等待。

此函数将当前进程置于不可中断的等待状态,所以等待不能够强制结束,并且等待的时间是MAX_SCHEDULE_TIMEOUT个系统时钟节拍,这是一个很长的时钟节拍,几乎是不会因等待超时而结束的。

wait_for_completion文件包含

#include <linux/completion.h>

wait_for_completion函数定义

在内核源码中的位置:linux-3.19.3/kernel/sched/completion.c

函数定义格式:

void wait_for_completion(struct completion *)

wait_for_completion输入参数说明

此函数的输入参数是struct completion结构体类型的指针,包含一个等待队列信息及等待队列的状态信息,等待队列的状态代表此等待队列是否被唤醒过,其定义及详细解释参考函数complete( )输入参数说明部分。

wait_for_completion返回参数说明

此函数的返回结果是void类型的变量,即不返回任何类型的结果。

wait_for_completion实例解析

编写测试文件:wait_for_completion.c

头文件引用及全局变量定义:

/*头文件引用*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/pid.h>
#include <linux/wait.h>
#include <linux/completion.h>
#include <linux/kthread.h>
MODULE_LICENSE("GPL");
/*全局变量定义*/
static struct completion comple;    //用于保存completion的状态
struct task_struct * old_thread;    //保存初始化进程的信息

子进程处理函数定义:

int my_function(void * argc)
{
    wait_queue_head_t head;
    wait_queue_t data;
    printk("in the kernel thread function! \n");
    init_waitqueue_head(&head);                    //初始化等待队列头元素
    init_waitqueue_entry(&data, current);          //用当前进程初始化等待队列元素
    add_wait_queue(&head, &data);                  //将当前进程插入到等待队列中
    schedule_timeout_uninterruptible(10);          //将等待队列置于不可中断的等待状态
    printk("the current pid is:%d\n", current->pid);  //显示当前进程的PID值
    printk("the state of the parent is:%ld\n", old_thread->state); //显示父进程的状态
    //complete(&comple);  //调用函数唤醒进程,并更改done字段的值
    printk("out the kernel thread function\n");
    return 0;
}

模块加载函数定义:

static int __init wait_for_completion_init(void)
{
    struct task_struct * result;
    wait_queue_t data;
    printk("into wait_for_completion_init.\n");
    old_thread = current;
    result=kthread_create_on_node(my_function, NULL, -1, "wait_for_completion");
                                          // 创建新进程
    wake_up_process(result);
    init_completion(&comple);             //初始化completion变量
    init_waitqueue_entry(&data, result); //用新进程初始化等待队列元素
    __add_wait_queue_tail(&(comple.wait), &data);       //将新进程加入等待队列的尾部
    wait_for_completion(&comple);        //阻塞进程,等待新进程的结束
    printk("the pid of new thread is :%d\n", result->pid);
                                          //显示函数kernel_thread( )函数的返回结果
    printk("the current pid is:%d\n", current->pid);    //显示当前进程的PID值
    printk("out wait_for_completion_init.\n");
    return 0;
}

模块退出函数定义:

static void __exit wait_for_completion_exit(void)
{
    printk("Goodbye wait_for_completion\n");
}

模块加载、退出函数调用:

module_init(wait_for_completion_init);
module_exit(wait_for_completion_exit);

实例运行结果及分析:

首先编译模块,执行命令insmod wait_for_completion.ko插入内核模块,会出现如图A所示的结果。

Linux内核API wait_for_completion

重新打开一个超级终端,输入命令dmesg -c会出现如图B所示的结果。

Linux内核API wait_for_completion

输入命令lsmod | head -6查看内核当前的模块信息,会出现如图C所示的结果。

Linux内核API wait_for_completion

重启系统,去掉子进程处理函数中对语句“complete(&comple); ”的注释,保存文件,重新编译、加载模块,输入命令dmesg -c,会出现如图D所示的结果。

Linux内核API wait_for_completion

结果分析:

对图A、图B、图C对应的测试程序和图D对应的测试程序都可以看出子进程处理函数都在父进程处理函数之前执行完毕。

对于第一个测试程序,图A的结果说明模块插入之后,模块进入无法结束的状态,通过Ctrl+C也无法强制结束程序的执行。图B的输出结果说明子进程处理函数成功执行完毕,并且在子进程执行时父进程的状态值是2,说明此时父进程处于不可中断的等待状态,也正好说明了图A的结果,即用Ctrl+C也无法终止程序的执行。此函数导致等待的时延是MAX_SCHEDULE_TIMEOUT个系统时钟节拍,这是一个很长的等待时间,几乎无法结束。父进程在函数wait_for_completion( )之后的内容没有显示,说明进程阻塞在函数wait_for_completion( )处。图B查看内核的模块,可以看出新插入的模块存在。

对于第二个测试程序,图D的输出结果说明子进程和父进程都能成功执行完毕,因为函数complete( )将等待队列唤醒,并且更改了输入参数的done字段的值,使其不等于0,从而结束了函数wait_for_completion( )的循环,使阻塞结束,父进程才得以执行。

说明
对于子进程处理函数中调用函数schedule_timeout_uninterruptible( )使子进程进入短暂的睡眠,是为了保证父进程中的函数wait_for_completion( )能够在子进程中显示父进程状态之前被执行,从而能看到函数wait_for_completion( )对当前进程的作用。

进程状态说明:

对于进程能够处于的状态,在函数__wake_up( )的进程状态说明部分有详细的说明。

Camera课程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

办公软件教程

Linux教程

计算机教程

大数据教程

开发工具教程