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);
}