5.3 IPIPE: 双内核交互之ipipe_flags
5.3.1 IPIPE改造struct thread_info之ipipe_flags
struct task_struct 是Linux内核用来描述一个进程或线程的最核心的数据结构。每个任务(这里可以理解为进程或者线程)都有一个对应的 task_struct 实例。它包含了任务的所有信息,比如状态、进程ID (PID)、调度信息以及其他特定于任务的各种信息。
struct task_struct 中包含了struct thread_info结构体(CONFIG_THREAD_INFO_IN_TASK=y,否则struct thread_info在内核栈的栈底)。struct thread_info 包含了低级别的任务数据,这些数据是汇编代码(entry.S)需要立即访问的。当从用户空间切换到内核空间时(如执行系统调用或发生中断),内核需要快速获取当前线程的一些基本信息来正确地保存现场(即当前执行环境的状态)并进行上下文切换。因此,这些信息必须存储在一个可以被迅速定位和访问的位置。

struct thread_info中的flags是一个标志位字段,用于存储与线程相关的低级别的状态信息或配置选项。例如,它可能用来标记线程是否被抢占、是否需要重新调度等。不同的位可以代表不同的含义,例如TIF_SIGPENDING、TIF_NEED_RESCHED等宏定义。这些宏定义以TIF_为前缀,意思是Thread Info Flags!

如上图,IPIPE在struct thread_info中新增了ipipe_flags,用于Xenomai/Linux双内核之间进行交互。
如下图,ipipe_flags不同的位可以代表不同的含义,例如TIP_MAYDAY、TIP_NOTIFY、TIP_HEAD和TIP_USERINTRET等4中情况。这些宏定义以TIP_为前缀,猜测意思是Thread Info Primary,因为Primary代表Head域。

5.3.2 ipipe_flags之TIP_HEAD
TIP_HEAD最简单,最好理解,代表进程当前运行在Head域。
当从root域迁移到head域,利用ipipe_set_ti_thread_flag设置TIP_HEAD;

当从head域进入root域,利用ipipe_clear_thread_flag清除TIP_HEAD.

5.3.3 ipipe_flags之TIP_NOTIFY
TIP_NOTIFY的机制是最复杂的!
(1)Xenomai线程必有TIP_NOTIFY标记
只要是绑定到xenomai core的进程或线程,一定有TIP_NOTIFY标记!除非收到IPIPE_KEVT_CLEANUP,代表任务退出才清除。
引用ipipe.rst中_binding段落中的说明:
The real-time core should inform the kernel about its intent to receive notifications about that task, by calling :c:func::`ipipe_enable_notifier` when such task is current.
include/linux/ipipe.h:
#define ipipe_enable_notifier(p) \
ipipe_set_ti_thread_flag(task_thread_info(p), TIP_NOTIFY)
内核态线程创建,会调用ipipe_enable_notifier设置TIP_NOTIFY
rtdm_task_init // kernel/xenomai/rtdm/drvlib.c
->xnthread_init // kernel/xenomai/thread.c
->__xnthread_init // kernel/xenomai/thread.c
->(如果是内核线程)spawn_kthread // kernel/xenomai/thread.c
->kthread_trampoline // kernel/xenomai/thread.c
->map_kthread // kernel/xenomai/thread.c
->pipeline_enable_kevents //kernel/xenomai/pipeline/kevents.c
->ipipe_enable_notifier // include/linux/ipipe.h
用户态线程创建过程中,也会调用ipipe_enable_notifier设置TIP_NOTIFY。
COBALT_SYSCALL(thread_create)
->__cobalt_thread_create // kernel/xenomai/posix/thread.c
|->pthread_create //kernel/xenomai/posix/thread.c
| ->xnthread_init // kernel/xenomai/thread.c
| ->__xnthread_init // kernel/xenomai/thread.c
|->cobalt_map_user // kernel/xenomai/posix/process.c
->pipeline_enable_kevents //kernel/xenomai/pipeline/kevents.c
->ipipe_enable_notifier // include/linux/ipipe.h
(2)TIP_NOTIFY分3类syscall/trap/kevent
TIP_NOTIFY定义的notifications,在代码中分为三类syscall、trap、kevent,在include/linux/ipipe_domain.h中有如下宏定义。
在这段宏定义中,后缀 P、E 和 R 分别代表不同的含义:
- P 代表 “Position”,即位置。在 __IPIPE_SYSCALL_P、__IPIPE_TRAP_P 和 __IPIPE_KEVENT_P 中,P 表示这些事件在某个优先级或位掩码中的位置。
- E 代表 “Enable”,即启用。在 __IPIPE_SYSCALL_E、__IPIPE_TRAP_E 和 __IPIPE_KEVENT_E 中,E 表示相应事件的启用标志。这些标志是通过将 1 左移相应的位置(由 P 定义)来设置的。
- R 代表 “Response” 或 “Result”,即响应或结果。在 __IPIPE_SYSCALL_R、__IPIPE_TRAP_R 和 __IPIPE_KEVENT_R 中,R 表示相应事件的响应或结果。这些值是通过将 8 左移相应的位置(由 P 定义)来设置的。- 我觉的是response的可能性比较大,估计是表达正在处理中的意思。
include/linux/ipipe_domain.h:
#define __IPIPE_SYSCALL_P 0
#define __IPIPE_TRAP_P 1
#define __IPIPE_KEVENT_P 2
#define __IPIPE_SYSCALL_E (1 << __IPIPE_SYSCALL_P)
#define __IPIPE_TRAP_E (1 << __IPIPE_TRAP_P)
#define __IPIPE_KEVENT_E (1 << __IPIPE_KEVENT_P)
#define __IPIPE_ALL_E 0x7
#define __IPIPE_SYSCALL_R (8 << __IPIPE_SYSCALL_P)
#define __IPIPE_TRAP_R (8 << __IPIPE_TRAP_P)
#define __IPIPE_KEVENT_R (8 << __IPIPE_KEVENT_P)
#define __IPIPE_SHIFT_R 3
#define __IPIPE_ALL_R (__IPIPE_ALL_E << __IPIPE_SHIFT_R)
/* ipipe_set_hooks(..., enables) */
#define IPIPE_SYSCALL __IPIPE_SYSCALL_E
#define IPIPE_TRAP __IPIPE_TRAP_E
#define IPIPE_KEVENT __IPIPE_KEVENT_E
上述三类notifications,应用在不同的场景,来看一下ipipe_set_hooks。
xenomai_init // kernel/xenomai/init.c
->cobalt_init // kernel/xenomai/posix/process.c
->pipeline_trap_kevents // kernel/xenomai/pipeline/kevents.c
int pipeline_trap_kevents(void)
{
init_hostrt();
ipipe_set_hooks(ipipe_root_domain, IPIPE_SYSCALL|IPIPE_KEVENT);
ipipe_set_hooks(&xnsched_primary_domain, IPIPE_SYSCALL|IPIPE_TRAP);
return 0;
}
ipipe_set_hooks 的函数,它用于设置root domain和head domain所对应的 ipipe_percpu_domain_data 结构体中的 coflags 字段,以反映新的钩子启用或禁用状态。为啥不把coflags直接定义在root domain和head domain数据结构中的呢?暂时不清楚。
不过,root domain和head domain的coflags是不同的哦!
对于root domain,设置coflags为IPIPE_SYSCALL|IPIPE_KEVENT,代表当实时进程运行在root domain,则可以调用__ipipe_notify_syscall 、 __ipipe_notify_trap告知xenomai处理此两类信号。
对于head domain,设置coflags为IPIPE_SYSCALL|IPIPE_TRAP,代表当实时进程运行在head domain,则可以调用__ipipe_notify_syscall 、 __ipipe_notify_kevent告知xenomai处理此两类信号。
其中,系统调用syscall是共同的,trap是root domain独有的,kevent是head domain独有的。
(3)TIP_NOTIFY需要hook钩子函数来响应
从上面的分析可知,notification是由IPIPE在不同场景,调用__ipipe_notify_syscall、__ipipe_notify_trap、__ipipe_notify_kevent三个接口发送出去的,那么谁是接收者?
Xenomai是接收者,它需要实现一系列hook函数,来完成notifications的处理。
第一,__ipipe_notify_syscall对应的钩子函数
kernel/xenomai/pipeline/syscall.c:
int ipipe_syscall_hook(struct ipipe_domain *ipd, struct pt_regs *regs)
int ipipe_fastcall_hook(struct pt_regs *regs)
系统调用syscall对应两个hook。
ipipe_fastcall_hook是系统调用快速处理通路,专门用于处理Xenomai实时线程运行在Head域时触发的Xenomai系统调用!
ipipe_syscall_hook是系统调用慢速处理通路,一般需要先把Xenomai实时线程从root domain切换到head domain之后,再处理系统调用。
ipipe_syscall_hook 这个函数的命名很有意思。
-
ipipe: 表明这是ipipe所需的函数
-
syscall:表明这是TIP_NOTIFY中的syscall notification
-
hook:这是一个预埋的钩子函数,需要co-kernel去实现!
第二,__ipipe_notify_trap对应的钩子函数
kernel/xenomai/pipeline/kevents.c:
int ipipe_trap_hook(struct ipipe_trap_data *data)
什么是TRAP?看起来是IPIPE自己创造的软件层面的概念。
arch/arm64/include/asm/ipipe_base.h:
/* ARM64 traps */
#define IPIPE_TRAP_MAYDAY 0 /* Internal recovery trap */
#define IPIPE_TRAP_ACCESS 1 /* Data or instruction access exception */
#define IPIPE_TRAP_SECTION 2 /* Section fault */
#define IPIPE_TRAP_DABT 3 /* Generic data abort */
#define IPIPE_TRAP_UNKNOWN 4 /* Unknown exception */
#define IPIPE_TRAP_BREAK 5 /* Instruction breakpoint */
#define IPIPE_TRAP_FPU_ACC 6 /* Floating point access */
#define IPIPE_TRAP_FPU_EXC 7 /* Floating point exception */
#define IPIPE_TRAP_UNDEFINSTR 8 /* Undefined instruction */
#define IPIPE_TRAP_ALIGNMENT 9 /* Unaligned access exception */
#define IPIPE_NR_FAULTS 10
宏定义的值(如IPIPE_TRAP_ACCESS、IPIPE_TRAP_SECTION、IPIPE_TRAP_DABT等)是用于标识不同类型的异常或陷阱(trap),这些值与ARM64架构的异常类型有一定的对应关系,但并不完全一致。这些宏定义的值是为了在软件层面上区分和处理不同类型的异常,而不是直接对应于硬件错误码。
例如,IPIPE_TRAP_ACCESS可能对应于ARM64架构中的数据访问异常,而IPIPE_TRAP_DABT可能对应于数据中止异常。这些宏定义的值是为了在软件层面上提供一个统一的接口,以便处理不同类型的异常,而不是直接与硬件错误码对应。
在实际的异常处理程序中,这些宏定义的值会被用来索引到相应的处理代码,以便对不同类型的异常进行处理。因此,虽然这些宏定义的值与ARM64 CPU的硬件错误码不完全一致,但它们在软件层面上是用于处理和区分不同类型的异常的关键。
举一个具体的例子,在entry.S中,同步异常el0_fpsimd_acc跳转到do_fpsimd_acc函数,IPIPE在其中插入了__ipipe_report_trap-> __ipipe_notify_trap调用。
do_fpsimd_acc会检查__ipipe_report_trap的返回值,如果返回1则直接return,代表Xenomai已经处理完毕了,不在需要Linux处理。如果返回0,代表继续向下执行原来Linux的处理逻辑。



紧接着,__ipipe_notify_trap如果发现此时在root domain,直接返回,并不需要xenomai处理trap。如果此时在head domain,则调用ipipe_trap_hook继续处理!

紧接着,调用ipipe_trap_hook,这是由xenomai来实现的,专门处理trap。

ipipe_trap_hook具体的实现就不展开了,发现它的返回值非常有意思:
#define KEVENT_PROPAGATE 0
#define KEVENT_STOP 1
再回到do_fpsimd_acc的逻辑,如果返回1则直接return,代表Xenomai已经处理完毕了,不在需要Linux处理。如果返回0,代表继续向下执行原来Linux的处理逻辑。
第三,__ipipe_notify_kevent对应的钩子函数
kernel/xenomai/pipeline/kevents.c:
int ipipe_kevent_hook(int kevent, void *data)
kevent是指一些纯的内核事件(似乎与硬件无关),可能会影响Xenomai的任务管理,所以需要通知Xenomai进行处理。
以下事件在(include/linux/ipipe_domain.h)中定义:
#define IPIPE_KEVT_SCHEDULE 0
#define IPIPE_KEVT_SIGWAKE 1
#define IPIPE_KEVT_SETSCHED 2 //已被废弃,不应该使用
#define IPIPE_KEVT_SETAFFINITY 3
#define IPIPE_KEVT_EXIT 4
#define IPIPE_KEVT_CLEANUP 5
#define IPIPE_KEVT_HOSTRT 6
#define IPIPE_KEVT_CLOCKFREQ 7
#define IPIPE_KEVT_USERINTRET 8
#define IPIPE_KEVT_PTRESUME 9
以IPIPE_KEVT_EXIT来举一个实际的例子。
SYSCALL_DEFINE1(exit, int, error_code) // __arm64_sys_exit, kernel/exit.c
-> do_exit // kernel/exit.c
-> __ipipe_report_exit(tsk); // include/linux/ipipe.h
->__ipipe_notify_kevent(IPIPE_KEVT_EXIT, p); //kernel/ipipe/core.c
__ipipe_notify_kevent会调用ipipe_root_only()确保当前运行在root域。

Xenomai实现的ipipe_kevent_hook,通过switch case语句根据不同的kevent,调用不同的函数处理。

5.3.4 ipipe_flags之TIP_MAYDAY
第一,什么是MAYDAY紧急情况
“MAYDAY” 作为紧急情况的信号,源于航空和海事通信领域。它是一个国际公认的无线电求救信号,用来表示船只、飞机或其他交通工具遇到了严重的、直接威胁到生命或安全的紧急状况。选择 “MAYDAY” 这个词是因为它在法语中的发音类似于 “m’aider” 或 “m’aidez”,意为“帮助我”。这个词清晰易读,即使在嘈杂或干扰严重的环境下也能被正确理解。
在Xenomai中,什么被定义成紧急情况?
(1)watchdog_handler 函数
watchdog_handler 函数,用于处理看门狗定时器的触发事件。这个函数的主要功能是检测软件死锁,并采取相应的措施来恢复系统的正常运行。
if (xnthread_test_state(curr, XNUSER)) {
printk(XENO_WARNING "watchdog triggered on CPU #%d -- runaway thread "
"'%s' signaled\n", xnsched_cpu(sched), curr->name);
xnthread_call_mayday(curr, SIGDEBUG_WATCHDOG);
} else {
printk(XENO_WARNING "watchdog triggered on CPU #%d -- runaway thread "
"'%s' canceled\n", xnsched_cpu(sched), curr->name);
xnthread_set_info(curr, XNKICKED|XNCANCELD);
}
如果是用户空间线程,则打印一条警告信息,表明看门狗定时器已经触发,并且当前线程可能是一个失控的线程。然后调用 xnthread_call_mayday -> pipeline_raise_mayday -> ipipe_raise_mayday 函数,向当前线程发送一个 SIGDEBUG_WATCHDOG 信号,强制线程退出primary模式,进入secondary模式,即从Xenomai迁移到Linux。
(2)处理SIGKICK信号
SIGKICK信号,用于强制线程退出主模式,即从Xenomai迁移到Linux:
__cobalt_kill ->xnthread_kick->__xnthread_kick->pipeline_raise_mayday
#define SIGSUSP (SIGRTMAX + 1) //挂起线程
#define SIGRESM (SIGRTMAX + 2) //恢复线程
#define SIGRELS (SIGRTMAX + 3) //解除阻塞线程
#define SIGKICK (SIGRTMAX + 4) //强制线程退出主模式
#define SIGDEMT (SIGRTMAX + 5) //将线程降级到 SCHED_OTHER 调度类
这些信号仅在使用 pthread_kill() 函数时可用,用于同步地挂起、恢复、解除阻塞线程,强制它们退出主模式,甚至将它们降级到 SCHED_OTHER 调度类。这些信号不能被阻塞、排队,甚至不能设置在信号集中。它们是非常不规范的,是为了满足特定的实时系统需求而提供的
第二,MAYDAY运行机制
TIP_MAYDAY仅仅是struct thread_info中的ipipe_flags的标志位。怎么告诉Xenomai去处理呢?需要利用TIP_NOTIFY中的trap机制,并且专门定义了一个TRAP类型IPIPE_TRAP_MAYDAY。
当利用ipipe_raise_mayday标记TIP_MAYDAY紧急情况时,需要确保其依赖的TIP_NOTIFY是否置位了。

IPIPE在处理系统调用(ipipe_handle_syscall)和中断(__ipipe_exit_irq)的过程中,会通过如下过程处理MAYDAY:
__ipipe_call_mayday
->__ipipe_notify_trap
->ipipe_trap_hook
->handle_mayday_event
->xnthread_relax
具体的展开过程如下:
当利用__ipipe_call_mayday处理TIP_MAYDAY时,实际上是调用__ipipe_notify_trap发送IPIPE_TRAP_MAYDAY给ipipe_trap_hook函数。

Xenomai实现的ipipe_trap_hook总是先处理IPIPE_TRAP_MAYDAY!

最终,handle_mayday_event调用xnthread_relax将实时线程切换到Linux中运行。
小结:TIP_MAYDAY定义了一种紧急情况,依赖TIP_NOTIFY中的trap机制,在不同的场景下,最终将实时线程切换到Linux中运行。
5.3.5 ipipe_flags之TIP_USERINTRET
已在前面的章节《2.4 EL0_IRQ 中断改造之返回》介绍过,不再重复。
1393

被折叠的 条评论
为什么被折叠?



