NuttX的学习笔记 9

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yt454287063/article/details/53545867

Task Control Interfaces

Task Control Interfaces

Scheduler locking interfaces

调度程序锁定接口。这种非标准接口是用于启用和禁用占先式和测试占先式目前启用。占先式优先级是32的一种中断相应方式。

sched_lock
int sched_lock(void);

这个函数禁用添加新任务。任务调用这个函数后将会是唯一任务。

sched_unlock

这个函数衰减抢占锁计数。通常这是搭配sched_lock()。解锁抢占有时需要多次调用sched_unlock()由于之前也多次调用sched_lock()。当lockCount递减为零,任何任务都有资格来抢占当前任务执行。这里和我理解的不一样,我以为是前一个函数将当前任务锁定为唯一任务,唯一任务是个什么意思,还有待试验中发现。

sched_lockcount

这个函数返回lockCount的当前值。如果为零,抢占启用;如果非零,这个值表明sched_lock()被称为执行线程。

(顺便这里说一下上一次的时间片问题,回显的tv_sec: 0为0而tv_nsec: 200000000 值这么大的原因。时间片就是系统分给实时进程的运行时间,一旦时间片用完,那么进程将被暂停,放到FIFO队列最后面等待下一次调度。sec自然就是秒,而nsec自然就是纳秒,因为在sched_rr_get_interval 内将sec赋值的语句是一个除法,结果是一个小数,而tv_sec的类型不是一个浮点型,故为0。)

Task synchronization interfaces

等待终止子任务。

waitpid
ipid_t waitpid(pid_t pid, int *stat_loc, int options);

这个看起来还是有点复杂的。首先这个函数涉及到配置选项 CONFIG_SCHED_HAVE_PARENT 。如果定义CONFIG_SCHED_HAVE_PARENT waitpid()将更符合规范。
当该配置未开启时,waitpid()只支持等待任何任务完成执行。
当该配置开启时,waitpid() 将使用 SIGCHLD (这个貌似很厉害的样子)以达到等待任何进程。

pid 的情况有以下几种情况:

pid 结果
pid < (pid_t) -1 取绝对值
pid = (pid_t) -1 等待知道任意一个子进程返回
pid = 0 等待于调用函数的子进程组内的所有子进程
pid > 0 等待制定 pid 子进程

options的值有以下几种情况:

options 在以下情况下返回或也会返回
WCONTINUED 等待的已停止但还未报道的子进程又被其他功能继续时
WNOHANG 没有已经退出的子进程
WUNTRACED 指定的子进程已经退出但还未被报道

这里比较复杂,实际使用中碰到的时候再说

waitid

#ifdef CONFIG_SCHED_HAVE_PARENT
int waitid(idtype_t idtype, id_t id, FAR siginfo_t *info, int options);
#endif

不配置CONFIG_SCHED_HAVE_PARENT 将不会有这个函数。而且作者写这个文档时这个 东西还没不完整。如果定义CONFIG_SCHED_HAVE_PARENT waitid()将更符合规范。
waitid仅仅支持等待一个特定的子任务。
这里先说明一下 pid 和 tid :

  • pid(Process Identification) 进程识别号
  • tid(Thread Identification) 线程识别号

总的来说,pid是指进程的。tid是指线程的。pid是唯一的,但是tid只是在在同一程下是唯一的。

idtype 意义
P_PID waitid()将等待pid为id的子进程
P_PGID waitid()将等待pgid为id的进程组内所有的子进程。
P_ALL waitid()将等待任何子进程并且忽略id

options 参数表:

idtype 意义
WEXITED 等待已终止的子进程
WSTOPPED 等待那些被因接收到信号而暂停的子进程
WCONTINUES 等待那些已经停止了但是又被继续的子进程
WNOHANG 如果没有子进程需要等待,那么立即返回
WNOWAIT 返回任务的状态改变信息后,还能在之后继续获取该状态信息

wait

Task Exit Hooks

好长一段,懒得看了。直接翻译:atexit()和on_exit()可以使用注册回调函数,当一个任务组执行终止。任务组的功能模拟是一个过程:这是一组由主要任务线程和所有pthreads的主要任务创建的线程或者其他的任务broup pthreads。任务组的成员共享某些资源等环境变量,文件描述符,文件流、套接字、pthread键和开放的消息队列。

atexit

int atexit(void (*func)(void));

使用该函数注册结束前执行的空函数。

on_exit

int on_exit(CODE void (*func)(int, FAR void *), FAR void *arg);

使用该函数注册结束前执行的任务。

Parent and Child Tasks

任务同步接口“历史上?”依赖父进程和子进程之间的关系的任务。但默认的,NuttX不使用任何父/子进程知识。然而,有三个重要的配置选项,可以改变这种情况。

CONFIG_SCHED_HAVE_PARENT

如果这个设置定义,它将使NuttX记得父任务新创的每个子任务的任务ID。这种它能支持使一些额外的特性(如SIGCHLD)和修改其他接口的行为。例如,它使waitpid()更标准完成通过限制我等的那个任务的调用者。

CONFIG_SCHED_CHILD_STATUS

如果这个选项被选中时,那么子进程退出后其退出状态标志将被保留。没有这个设置 wait()waitpid()waitid() 可能会失败。

CONFIG_PREALLOC_CHILDSTATUS

为了防止失控的子进程状态分配和提高分配性能,子任务退出状态结构在系统启动时预先分配。此设置确定子进程的数量状态结构,将预先分配。如果这个设置没有定义或定义为零值为2 X MAX_TASKS使用。

又到了开始测试的时候了。

任务控制接口实验

按照我的理解先测试第一个。将 hello_task 锁住,再添加其他任务。代码:(顺便说一下我发现这个ifdef-endif很好用,既能保存之前的代码,而且还能轻松地使用define启用)
这里先停一下,任务的锁定效果并不好,不知道哪里出了问题。之前有个让任务让出CPU的函数也是这样。输出结果丝毫不受影响。搞得我都不想再测试任务了。下一个解锁的也跳过。

以下的 任务不是必须配置 CONFIG_SCHED_HAVE_PARENT ,路径:-> RTOS Features -> Tasks and Scheduling -> Support parent/child task relationships 先不配置,测试后再配置进行比对。

(前天的测试一点都不理想,关于 waitpid ,昨天刚好赶上DOTA2的7.0更新,大圣简直了,出场率100%,玩了一下午,一天都没有看代码。)

今天测试有点意外, waitpid 突然又能用了。这让我喜出望外。测试代码宏定义段:(全部代码在最后面)

#define Started_print_hello
#define Wait_print_hello_with_waitpid
#define Print_Hello_World

测试结果:

nsh> hello_task 5
hello_task: Started print_hello at PID=9
print_hello: Hello 0
print_hello: Hello 1
print_hello: Hello 2
print_hello: Hello 3
print_hello: Hello 4
hello_task: Waiting print_hello success
hello_task: Hello World 0
hello_task: Hello World 1
hello_task: Hello World 2
hello_task: Hello World 3
hello_task: Hello World 4
nsh>

效果很理想,完全达到预期。
切回去测试之前的代码,果然 lock 的不工作问题也解决了。代码宏定义段改为:

#define Started_print_hello
#define Lock_hello_task_with_sched_lock
#define Print_Hello_World

结果和上次实验的结果一样。
再来一个waitid。
首先在config里启动该选项。
-> RTOS Features -> Tasks and Scheduling -> Support parent/child task relationships
宏定义段:

#define Started_print_hello
#define Wait_print_hello_with_waitid
#define Print_Hello_World

达到相同的效果。

wait() 寻找到该代码的定义段时发现:

pid_t wait(FAR int *stat_loc) {
    return waitpid((pid_t) -1, stat_loc, 0);
}

这不就是waitpid() 的等待所有子进程的功能么!
我就再开一个子进程print_world,并且输出的的是print_hello 的两倍。之后发现结果是当其中一个 print_hello 结束后主进程就停止等待了,所以,我重新阅读了文档,发现之前有个小错误,waitpid(-1,...)的功能是等待所有子进程直到有任意一个子进程结束,并不是等待所有。下面一个实验当然就是waitpid(0,...)
结果,出现了错误:

hello_task: ERROR: Failed to all child task: No child processes

看样子之前的问题又暴露出来了:由进程创建的进程是不是子进程。
查了一些资料,结果是,子进程(只能?)由vfork 创建。
之前写的例子直接搬过来。重新整理代码,结果是无论如何我都不能让父进程先于子进程。查找 vfork 的代码,并没有找到,这就很诡异了。但是在这里,我找到了task_vforkstart这个函数。这个函数内有这样一段代码:

    /* Get the assigned pid before we start the task */

    pid = (int) child->cmn.pid;

    /* Activate the task */

    ret = task_activate((FAR struct tcb_s *) child);
    if (ret < OK) {
        task_vforkabort(child, -ret);
        return ERROR;
    }

    /* Since the child task has the same priority as the parent task, it is
     * now ready to run, but has not yet ran.  It is a requirement that
     * the parent environment be stable while vfork runs; the child thread
     * is still dependent on things in the parent thread... like the pointers
     * into parent thread's stack which will still appear in the child's
     * registers and environment.
     *
     * We do not have SIG_CHILD, so we have to do some silly things here.
     * The simplest way to make sure that the child thread runs to completion
     * is simply to yield here.  Since the child can only do exit() or
     * execv/l(), that should be all that is needed.
     *
     * Hmmm.. this is probably not sufficient.  What if we are running
     * SCHED_RR?  What if the child thread is suspended and rescheduled
     * after the parent thread again?
     */

    /* We can also exploit a bug in the execv() implementation:  The PID
     * of the task exec'ed by the child will not be the same as the PID of
     * the child task.  Therefore, waitpid() on the child task's PID will
     * accomplish what we need to do.
     */

    rc = 0;

    (void) waitpid(pid, &rc, 0);
#endif

    return pid;

这样看来,由于没有SIG_CHILD,所以在子进程开始之前,就已经处于 waitpid 状态,而waitpid(pid, &rc, 0)中的pid在上面就是由child->cmn.pid赋值的,总的来说就是父进程在等待子进程先于父进程结束。难怪子进程先于父进程而不是两个进程一起运行,当然,父进程先于子进程的话,父进程一结束,那子进程不久崩溃了么,父进程先于子进程肯定是不行的。除非有这里提到的SIG_CHILD。这个等待所有子进程的就算是完成了。当然只有一个子进程需要等待的,如果在子进程中再调用一次 vfork 便会出现嵌套的 waitpid 看样子想要让父进程等待很多子进程还远着呢。

下一个就是两个任务在执行完成前调用其他函数的函数。
使用atexit()之前还需要在configure中开启CONFIG_SCHED_ATEXIT,路径 -> RTOS Features -> RTOS hooks -> Enable atexit() API然后设定数量上限。代码宏定义部分:

#define atexit_test

结果不言而喻。但是另一个出了问题。完全没有运行。其定义令我费解:

int on_exit(CODE void (*func)(int, FAR void *), FAR void *arg);

…就看在我心情不错的份上先放它一马。本章结束。最后,所有代码:

#include <sched.h>
#include <nuttx/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>

/*********************************************************
 * Pre-processor Definitions
*********************************************************/
//#define here
/*********************************************************
 * Private Functions
*********************************************************/
#if defined(Started_print_hello)||defined(Started_print_world)\
    ||defined(Print_Hello_World)||defined(on_exit_test)
int str2num(const char *s) {
    int rt = 0;
    while (*s >= '0' && *s <= '9') {
        rt = rt * 10 + (*s - '0');
        s++;
    }
    return rt;
}
#endif

#ifdef Started_print_hello
int print_hello(int argc, char *argv[]) {

    int i;
    int a = str2num(argv[1]);
    for (i = 0; i < a; ++i) {
        sched_yield();
        printf("%s: Hello %d\n", argv[0], i);
    }
    exit(EXIT_SUCCESS);
}
#endif

#ifdef Started_print_world
int print_world(int argc, char *argv[]) {

    int i;
    int a = str2num(argv[1]);
    for (i = 0; i < (a * 2); ++i) {
        printf("%s: World %d\n", argv[0], i);
    }
    exit(EXIT_SUCCESS);
}
#endif

#ifdef atexit_test
void fun(void) {
    printf("fun\n");
}
#endif

/*********************************************************
 * Public Functions
*********************************************************/

/*********************************************************
 * hello_task
*********************************************************/
int hello_task_main(int argc, char *argv[]) {

    int ret = 0;
    pid_t hello_task_pid;
    hello_task_pid = getpid();

#ifdef Started_print_hello
    pid_t print_hello_pid;
    print_hello_pid = task_create("print_hello",
            SCHED_PRIORITY_DEFAULT, 2048, print_hello, &argv[1]);
    if (print_hello_pid < 0) {
        int errcode = errno;
        printf("%s: ERROR: Failed to start print_hello: %s\n", argv[0],
                strerror(errcode));
        exit(EXIT_FAILURE);
    } else
    printf("%s: Started print_hello at PID=%d\n", argv[0], print_hello_pid);
#endif

#ifdef Started_print_world
    pid_t print_world_pid;
    print_world_pid = task_create("print_world",
            SCHED_PRIORITY_DEFAULT, 2048, print_world, &argv[1]);
    if (print_world_pid < 0) {
        int errcode = errno;
        printf("%s: ERROR: Failed to start print_world: %d\n", argv[0],
                strerror(errcode));
        exit(EXIT_FAILURE);
    } else
    printf("%s: Started print_world at PID=%d\n", argv[0], print_world_pid);
#endif

#ifdef Wait_child_task_with_waitpid
    ret = waitpid(print_hello_pid, NULL, 0);
    if (ret == ((pid_t) -1)) {
        int errcode = errno;
        printf("%s: ERROR: Failed to wait print_hello: %s\n", argv[0],
                strerror(errcode));
        exit(EXIT_FAILURE);
    } else
    printf("%s: Waiting print_hello success\n", argv[0]);
#endif

#ifdef Wait_child_task_with_waitid
    ret = waitid(P_PID, print_hello_pid, NULL, 0);
    if (ret == ((pid_t) -1)) {
        int errcode = errno;
        printf("%s: ERROR: Failed to wait print_hello: %s\n", argv[0],
                strerror(errcode));
        exit(EXIT_FAILURE);
    } else
    printf("%s: Waiting print_hello success\n", argv[0]);
#endif

#ifdef Wait_child_task_with_wait
    ret = wait(NULL);
    if (ret == ((pid_t) -1)) {
        int errcode = errno;
        printf("%s: ERROR: Failed to wait child task: %s\n", argv[0],
                strerror(errcode));
        exit(EXIT_FAILURE);
    } else
    printf("%s: Waiting child task success\n", argv[0]);
#endif

#ifdef Lock_hello_task_with_sched_lock
    printf("%s: Lock Count: %d\n", argv[0], sched_lockcount());
    ret = sched_lock();
    if (ret) {
        printf("%s: Lock myself Fail\n", argv[0]);
    } else {
        printf("%s: Lock myself Success\n", argv[0]);
        printf("%s: Lock Count: %d\n", argv[0], sched_lockcount());
    }
#endif

#ifdef Print_Hello_World
    {
        int i;
        int a = str2num(argv[1]);
        if (a == 0)
        printf("%s: There is nothing to say%d\n", argv[0], i);
        else
        for (i = 0; i < a; ++i) {
            printf("%s: Hello World %d\n", argv[0], i);
        }
    }
#endif

#ifdef atexit_test
    atexit(fun);
#endif

    exit(EXIT_SUCCESS);
}

猜你喜欢

转载自blog.csdn.net/yt454287063/article/details/53545867