5.4 IPIPE: 双内核系统调用
5.4.1 IPIPE劫持系统调用的流程与场景
参考《5.1.2 内核层:ARM64 Linux系统调用的流程》,先回顾一下ARM64 Linux系统调用的过程:
第一步,从el0_sync跳转到el0_svc。
第二步,从el0_svc跳转到c函数el0_svc_handler
第三步,从el0_svc_handler调用el0_svc_common
第四步,el0_svc_common调用invoke_syscall
在第四步的el0_svc_common函数中,IPIPE是在调用invoke_syscall之前,调用ipipe_handle_syscall进行系统调用的劫持。下图展示的是Xenomai系统调用read的堆栈。

考虑所有因素,在ipipe_handle_syscall需要处理6种场景。在ipipe_handle_syscall函数中,设计了3个代码通路,来处理上述6中场景。
| ipipe_handle_syscall 6种场景的处理 | Xenomai系统调用 (系统调用号>=0x1000 0000) | Linux系统调用 (系统调用号<0x1000 0000) |
|---|---|---|
| Xenomai实时线程 运行在Head domain | 场景1: ipipe_fastcall_hook | 场景2: __ipipe_notify_syscall |
| Xenomai实时线程 运行在Root domain | 场景3: __ipipe_notify_syscall | 场景4: __ipipe_notify_syscall |
| Linux线程 | 场景5: __ipipe_notify_syscall | 场景6: 正常返回给Linux处理 |
把上面的表格,对应到ipipe_handle_syscall函数的实现,就会发现代码的实现思路突然清楚了。 特别是场景6,一目了然,不需要展开。

在ipipe.rst中,代码通路ipipe_fastcall_hook又被称为快速处理路径(fast path),而__ipipe_notify_syscall又被称为慢速路径(slow path)。
为什么?
ipipe_fastcall_hook直接调用handle_head_syscall执行Xenomai的系统调用函数。
反观__ipipe_notify_syscall,逻辑比较复杂,用next/goto next反复调用ipipe_syscall_hook函数。在ipipe_syscall_hook函数中,如果当前是head domain则调用handle_head_syscall,如果当前是root domain则调用handle_root_syscall。
__ipipe_notify_syscall有个非常特殊的地方,在第一次调用ipipe_syscall_hook函数之前,总是把current domain改成head domain(第1229行)。这样在ipipe_syscall_hook函数中必然会调用handle_head_syscall。

总结:ipipe_handle_syscall劫持系统调用后,需要处理6中场景,分为3个代码路径进行处理:快速、慢速和正常路径!接下来的章节会结合实例实际分析快速和慢速路径!

Linux系统调用,都回归到invoke_syscall调用Linux系统调用函数
Xenomai系统调用,如果运行在root域,则在handle_root_syscall中处理
Xenomai系统调用,如果运行在head域,则在handle_head_syscall中处理
运行在head域,调用Linux系统调用,需要先切换到root域
运行在root域,调用Xenomai系统调用,需要先切换到head域。
5.4.2 场景1之运行在Head域Xenomai线程调用Xenomai系统调用read
参考《5.2.2 用户层:用latency演示Xenomai系统调用》,latency 是 Xenomai/Cobalt 实时内核提供的一个工具,用于测量系统的实时性能,特别是上下文切换和中断响应的延迟。它启动后,会新建一个display线程用于打印信息;会新建一个sampling线程用于延迟采样。
本文分析samping作为Xenomai实时线程,调用Xenomai系统调用read的过程:
-
第一步,ipipe_handle_syscall
-
第二步,handle_head_syscall
-
第三步,返回到ipipe_handle_syscall的第1187行
-
第四步,返回非0值到el0_svc_common第110行
第一步,ipipe_handle_syscall

第1186行,条件1(nr >= nr_syscalls)成立,因为从用户层传下来的write系统调用号0x10000051远大于Linux自身的系统调用数量nr_syscalls(294),具体参考《5.2 ARM64 Xenomai系统调用》。条件2(local_flags & _TIP_HEAD)成立,因为local_flags即ti->ipipe_flags,值为6。参考《5.3.2 ipipe_flags之TIP_HEAD》,_TIP_HEAD代表线程当前运行在head domain。条件1和条件2是与的关系,所以第1186行的if成立,会跳转到第1187行的快速路径ipipe_fastcall_hook。
第1187行,调用ipipe_fastcall_hook。在这个快速路径中,直接调用handle_head_syscall。
int ipipe_fastcall_hook(struct pt_regs *regs)
{
int ret;
ret = handle_head_syscall(false, regs);
XENO_BUG_ON(COBALT, ret == KEVENT_PROPAGATE);
return ret;
}
第二步,handle_head_syscall
handle_head_syscall函数比较长,分成两个部分来解释
(1)handle_head_syscall第483~第513行

第498行,通过__xn_syscall得到bind在xenomai中的系统调用号81。具体原理参考《5.2.3 内核层:Xenomai系统调用的流程》。
第512行,从Xenomai系统调用表中,找出bind系统调用的CoBaLt_read函数指针。
第513行,从Xenomai系统模式表中,找出bind系统调用的调用模式。参考《5.2.4 内核层:Xenomai系统调用的定义》,在kernel/xenomai/posix/syscall_entries.h找到read系统调用模式为__COBALT_MODE(read, handover) ,即80(0x50)。
kernel/xenomai/posix/syscall.c:
/* Exec in current domain. */
#define __xn_exec_current 0x10
/* Attempt syscall restart in the opposite domain upon -ENOSYS. */
#define __xn_exec_adaptive 0x40
/* Hand over mode selection to syscall. */
#define __xn_exec_handover (__xn_exec_current|__xn_exec_adaptive)
(2)handle_head_syscall第578~第557行

第578行,因为bind系统调用模式为__COBALT_MODE(bind, lostage),条件成立。
第562行,caller_is_relaxed是false,不会跳转到第588行返回。
第598行,获取当前进程调用系统调用的参数,存入args。
第600行,调用CoBaLt_read,再继续调用rtdm_fd_read函数。
COBALT_SYSCALL(read, handover,
(int fd, void __user *buf, size_t size))
{
return rtdm_fd_read(fd, buf, size);
}
在此之后,会根据CoBaLt_read的返回值执行一些逻辑,最终返回ipipe_fastcall_hook,在返回到ipipe_handle_syscall的第1187行。
第三步,返回到ipipe_handle_syscall的第1187行

第1187行,从ipipe_fastcall_hook返回,完全不检查返回值。
第1188行,重新读取当前任务的ipipe_flags。
第1189~1192行,如果当前仍然运行在HEAD域,检查是否有_TIP_MAYDAY标记,如果有,参照《5.3.4 ipipe_flags之TIP_MAYDAY》,进行处理,如果没有则返回1.
第1193行~1195行,如果当前切换到了root域,回放root domain的中断,返回-1.
无论上述判断如何,最终从ipipe_handle_syscall返回的值,肯定是非0值。
第四步,返回非0值到el0_svc_common第110行

第111行,因为ret等于-1或1,条件成立,最终在第117行返回,完成系统调用!
第131行,此行完全没有机会也没有必要执行了!
5.4.3 场景2之运行在Head域Xenomai线程调用Linux系统调用getpid
参考《5.2.2 用户层:用latency演示Xenomai系统调用》,latency 是 Xenomai/Cobalt 实时内核提供的一个工具,用于测量系统的实时性能,特别是上下文切换和中断响应的延迟。它启动后,会新建一个display线程用于打印信息;会新建一个sampling线程用于延迟采样。
通过条件断点,在ipipe_handle_syscall第1200行处,抓到Xenomai实时线程运行在head域时,对Linux系统调用getpid的调用。
第一步,ipipe_handle_syscall

第1199行,条件1(local_flags & _TIP_NOTIFY)成立,因为local_flags即ti->ipipe_flags值为6(_TIP_HEAD | _TIP_NOTIFY)。参考《5.3.2 ipipe_flags之TIP_HEAD》,_TIP_HEAD代表线程当前运行在head domain。参考《5.3.3 ipipe_flags之TIP_NOTIFY》,只要是绑定到xenomai core的进程或线程,一定有TIP_NOTIFY标记!条件2(nr >= nr_syscalls)不成立,因为nr是172,小于Linux自身的系统调用数量nr_syscalls(294)。条件1和条件2是逻辑或的关系,所以第1199行的if判断成立,走到第1200行慢速路径__ipipe_notify_syscall。
第1200行,走到慢速路径__ipipe_notify_syscall
第二步,__ipipe_notify_syscall

第1224行,通过__ipipe_current_domain获取当前所在的domain,存入变量caller_domain和this domain。从左上角的locals变量的值能够确认,当前所在domain是head domain。
第1225行,设置变量ipd为ipipe_head_domain即head domain。
第1226行,此处定义了标签next:,在不同的场景下可能会通过goto next跳转过来,形成循环。
第1227行,通过ipd(head domain)获取当前cpu对应的struct ipipe_percpu_domain_data head指针,存入p。
第1228行,参考《5.3.3 ipipe_flags之TIP_NOTIFY》,此处判断必然成立。
第1229行,设置当前cpu的上下文struct ipipe_percpu_data中的curr指向存在p中保存的ipipe_percpu_domain_data head指针。
第1232行,调用ipipe_syscall_hook。在ipipe_syscall_hook中,如果判断当前是secondary domain(即root domain)则调用handle_root_syscall。而因为在__ipipe_notify_syscall的第1229行已经把当前domain设置为head domain,所以ipipe_syscall_hook最终会调用handle_head_syscall。
int ipipe_syscall_hook(struct ipipe_domain *ipd, struct pt_regs *regs)
{
if (unlikely(is_secondary_domain()))
return handle_root_syscall(regs);
return handle_head_syscall(ipd != &xnsched_primary_domain, regs);
}
第三步,handle_head_syscall
handle_head_syscall函数比较长,分成两个部分来解释
(1)handle_head_syscall第494~495行

第494行,使用__xn_syscall_p判断系统调用号是否为Xenomai系统调用号。原理参考《5.2.3 内核层:Xenomai系统调用的流程》。当前是Linux系统调用,跳转到495行。
第495行,goto linux_syscall跳转到第647行linux_syscall标签出。
(2)handle_head_syscall第647~679行

第648行,xnsched_root_p 是一个内联函数,用于检查当前线程是否处于 root domain(即 Linux 非实时调度域)。该函数通过调用 xnthread_test_state 来测试当前线程的状态标志位 XNROOT,从而判断线程是否运行在 root domain。当前场景下,task_struct中的ipipe_flags有TIP_HEAD标记,肯定是运行在head domain,此处判断不成立,继续向下执行。
第669行,调用xnthread_relax(1, SIGDEBUG_MIGRATE_SYSCALL); 用于将当前线程从 head domain 切换到 root domain。参数 1表示线程需要立即切换,而不是等待调度机会。参数 SIGDEBUG_MIGRATE_SYSCALL是一个调试信号,用于在调试时跟踪线程的切换行为。
第671行,返回KEVENT_PROPAGATE(0)到第二步中的__ipipe_notify_syscall第1232行。
第四步,返回KEVENT_PROPAGATE到__ipipe_notify_syscall第1232行

第1232行,ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。
第1235行,因为在第1232行ipipe_syscall_hook执行过程中,从head domain切换到root domain,所以第1235行条件成立,跳转到第1237行。
第1237行,设置this_domain为当前domain即root domain。
第1242行,根据1237行,判断条件成立,走到第1243行。
第1243行,条件1(ipd != ipipe_root_domain)成立;条件2(ret == 0)成立,因为ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。所以跳转到第1244行。
第1244~1245行,设置ipd为root domain,通过goto next跳转到第1226行。
第1229行,设置当前domain为root domain。
第1232行,进入ipipe_syscall_hook之后,因为当前domain为root domain,所以指向handle_root_syscall函数。
第五步,handle_root_syscall函数

第699行,使用__xn_syscall_p判断系统调用号是否为Xenomai系统调用号。原理参考《5.2.3 内核层:Xenomai系统调用的流程》。当前是Linux系统调用,跳转到701行。
第701行,返回KEVENT_PROPAGATE(0)到__ipipe_notify_syscall第1232行。
第六步,返回KEVENT_PROPAGATE(0)到__ipipe_notify_syscall第1232行

第1232行,ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。
第1242行,根据1239行,判断条件成立,走到第1243行。
第1243行,条件1(ipd != ipipe_root_domain)不成立;条件2(ret == 0)成立,因为ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。所以不可能走到1245行执行goto next,至此next/go next循环结束。
第1259行,KEVENT_PROPAGATE(0)到ipipe_handle_syscall第1200行。
第七步,KEVENT_PROPAGATE(0)到ipipe_handle_syscall第1200行

第1202行,重新读取ti->ipipe_flags,因为已经切换到root domain,所以其中_TIP_HEAD标记已经没有了,此处判断不成立了!
第1204行,因为ret等于KEVENT_PROPAGATE(0),所以跳转到第1208行,返回0到el0_svc_common第110行。
第八步,返回0到el0_svc_common第110行

第110行,从ipipe_handle_syscall返回0.
第131行,通过invoke_syscall调用Linux原生系统调用gitpid的系统调用函数__arm64_sys_gitpid! 具体参考《5.1.2 内核层:ARM64 Linux系统调用的流程》。
至此,完成了此场景的分析!
5.4.4 场景3之运行在root域Xenomai线程调用Xenomai系统调用thread_setname
参考《5.2.2 用户层:用latency演示Xenomai系统调用》,latency 是 Xenomai/Cobalt 实时内核提供的一个工具,用于测量系统的实时性能,特别是上下文切换和中断响应的延迟。它启动后,会新建一个display线程用于打印信息;会新建一个sampling线程用于延迟采样。
通过条件断点,在ipipe_handle_syscall第1200行处,抓到Xenomai实时线程运行在root域时,对Xenomai系统调用thread_setmode的调用。
第一步,ipipe_handle_syscall

第1199行,条件1(local_flags & _TIP_NOTIFY)成立,因为local_flags即ti->ipipe_flags,值为_TIP_NOTIFY (2)。参考《5.3.3 ipipe_flags之TIP_NOTIFY》,只要是绑定到xenomai core的进程或线程,一定有TIP_NOTIFY标记!条件2(nr >= nr_syscalls)成立,因为nr是0x10000003,大于Linux自身的系统调用数量nr_syscalls(294)。条件1和条件2是逻辑或的关系,所以第1199行的if判断成立,走到第1200行慢速路径__ipipe_notify_syscall。
第1200行,走到慢速路径__ipipe_notify_syscall
第二步,__ipipe_notify_syscall

第1224行,通过__ipipe_current_domain获取当前所在的domain,存入变量caller_domain和this domain。从左上角的local变量的值能够确认,当前所在domain是ipipe_root即root domain。
第1225行,设置变量ipd为ipipe_head_domain即head domain。
第1226行,此处定义了标签next:,在不同的场景下可能会通过goto next跳转过来,形成循环。
第1227行,通过ipd(head domain)获取当前cpu对应的struct ipipe_percpu_domain_data head指针,存入p。
第1228行,参考《5.3.3 ipipe_flags之TIP_NOTIFY》,此处判断必然成立。
第1229行,设置当前cpu的上下文struct ipipe_percpu_data中的curr指向存在p中保存的ipipe_percpu_domain_data head指针。
第1232行,调用ipipe_syscall_hook,在图中也展示了此函数的定义内容。在ipipe_syscall_hook中,如果判断当前是secondary domain(即root domain)则调用handle_root_syscall。而因为在__ipipe_notify_syscall的第1229行已经把当前domain设置为head domain,所以ipipe_syscall_hook最终会调用handle_head_syscall。
第三步,handle_head_syscall
handle_head_syscall函数比较长,分成两个部分来解释
(1)handle_head_syscall第483~第513行

第498行,通过__xn_syscall得到bind在xenomai中的系统调用号3。具体原理参考《5.2.3 内核层:Xenomai系统调用的流程》。
第512行,从Xenomai系统调用表中,找出thread_setmode系统调用的CoBaLt_thread_setmode函数指针。
第513行,从Xenomai系统模式表中,找出thread_setmode系统调用的调用模式。参考《5.2.4 内核层:Xenomai系统调用的定义》,在kernel/xenomai/posix/syscall_entries.h找到thread_setmode系统调用模式为__COBALT_MODE(thread_setmode, primary),代表必须在head domain中运行。
kernel/xenomai/posix/syscall.c:
/* Syscall must run into the Xenomai domain. */
#define __xn_exec_histage 0x2
/* Shadow syscall: caller must be mapped. */
#define __xn_exec_shadow 0x4
/* Shorthand for shadow syscall in Xenomai space. */
#define __xn_exec_primary (__xn_exec_shadow|__xn_exec_histage)
(2)handle_head_syscall第551~第588行

第578行,因为thread_setmode系统调用模式为__COBALT_MODE(thread_setmode, primary),条件成立。
第587行,caller_is_relaxed是true,所有会跳转到第557行。
第588行,返回KEVENT_PROPAGATE(0)到第二步中的__ipipe_notify_syscall第1232行。
第四步,返回KEVENT_PROPAGATE到__ipipe_notify_syscall第1232行

第1232行,ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。
第1235行,因为在第1232行ipipe_syscall_hook执行过程中,当前domain一直是ipd所指向的head domain(第二步中初始化),所以第1235行条件不成立,跳转到第1239行。
第1239行,设置当前domain为this_domain即root domain(第二步中初始化)。
第1242行,根据1239行,判断条件成立,走到第1243行。
第1243行,条件1(ipd != ipipe_root_domain)成立;条件2(ret == 0)成立,因为ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。所以跳转到第1244行。
第1244~1245行,设置ipd为root domain,通过goto next跳转到第1226行。
第1229行,设置当前domain为root domain。
第1232行,进入ipipe_syscall_hook之后,因为当前domain为root domain,所以指向handle_root_syscall函数。
第五步,handle_root_syscall函数
(1)第712~713行

第712行,handler指向系统调用函数CoBaLt_thread_setmode.
第713行,sysflags的值为6,因为thread_setmode系统调用模式为__COBALT_MODE(thread_setmode, primary)。
(2)第717~791行

第724行,sysflags的值为6,因为thread_setmode系统调用模式为__COBALT_MODE(thread_setmode, primary)。此处判断成立。
第730行,因为thread_setmode系统调用模式为__COBALT_MODE(thread_setmode, primary),所以需要调用xnthread_harden,将当前任务(root域中的任务)迁移到head域。
第735行,设置switched=1,代表任务发生了域的迁移。
第746行,struct task_struct *p 指向当前进程的task_struct结构体。
第747行,得到传递给系统调用bind的参数,存入args。
第749行,调用系统调用CoBaLt_thread_setmode。
第791行,return KEVENT_STOP(1)到第二步中的__ipipe_notify_syscall第1232行。
第六步,返回KEVENT_STOP到__ipipe_notify_syscall第1232行

第1232行,ipipe_syscall_hook返回KEVENT_STOP(1)。
第1242行,根据1239行,判断条件成立,走到第1243行。
第1243行,条件1(ipd != ipipe_root_domain)不成立;条件2(ret == 0)不成立,因为ipipe_syscall_hook返回KEVENT_STOP(1)。所以不可能走到1245行执行goto next,至此next/go next循环结束。
第1259行,返回KEVENT_STOP(1)到ipipe_handle_syscall第1200行。
第七步,返回KEVENT_STOP(1)到ipipe_handle_syscall第1200行

第1204行,因为ret等于KEVENT_STOP(1),所以跳转到第1205行,返回-1到el0_svc_common第110行。
第八步,返回-1到el0_svc_common第110行

第111行,因为ret等于-1,条件成立,最终在第117行返回,完成系统调用!
第131行,此行完全没有机会也没有必要执行了!
5.4.5 场景4之运行在root域Xenomai线程调用Linux系统调用read
参考《5.2.2 用户层:用latency演示Xenomai系统调用》,latency 是 Xenomai/Cobalt 实时内核提供的一个工具,用于测量系统的实时性能,特别是上下文切换和中断响应的延迟。它启动后,会新建一个display线程用于打印信息;会新建一个sampling线程用于延迟采样。
通过条件断点,在ipipe_handle_syscall第1200行处,抓到Xenomai实时线程运行在root域时,对Linux系统调用read的调用。
第一步,ipipe_handle_syscall

第1199行,条件1(local_flags & _TIP_NOTIFY)成立,因为local_flags即ti->ipipe_flags,值为_TIP_NOTIFY (2)。参考《5.3.3 ipipe_flags之TIP_NOTIFY》,只要是绑定到xenomai core的进程或线程,一定有TIP_NOTIFY标记!条件2(nr >= nr_syscalls)不成立,因为nr是63,小于Linux自身的系统调用数量nr_syscalls(294)。条件1和条件2是逻辑或的关系,所以第1199行的if判断成立,走到第1200行慢速路径__ipipe_notify_syscall。
第1200行,走到慢速路径__ipipe_notify_syscall
第二步,__ipipe_notify_syscall

第1224行,通过__ipipe_current_domain获取当前所在的domain,存入变量caller_domain和this domain。从左上角的local变量的值能够确认,当前所在domain是ipipe_root即root domain。
第1225行,设置变量ipd为ipipe_head_domain即head domain。
第1226行,此处定义了标签next:,在不同的场景下可能会通过goto next跳转过来,形成循环。
第1227行,通过ipd(head domain)获取当前cpu对应的struct ipipe_percpu_domain_data head指针,存入p。
第1228行,参考《5.3.3 ipipe_flags之TIP_NOTIFY》,此处判断必然成立。
第1229行,设置当前cpu的上下文struct ipipe_percpu_data中的curr指向存在p中保存的ipipe_percpu_domain_data head指针。
第1232行,调用ipipe_syscall_hook,在图中也展示了此函数的定义内容。在ipipe_syscall_hook中,如果判断当前是secondary domain(即root domain)则调用handle_root_syscall。而因为在__ipipe_notify_syscall的第1229行已经把当前domain设置为head domain,所以ipipe_syscall_hook最终会调用handle_head_syscall。
第三步,handle_head_syscall
handle_head_syscall函数比较长,分成两个部分来解释
(1)handle_head_syscall第494~495行

第494行,使用__xn_syscall_p判断系统调用号是否为Xenomai系统调用号。原理参考《5.2.3 内核层:Xenomai系统调用的流程》。当前是Linux系统调用,跳转到495行。
第495行,goto linux_syscall跳转到第647行linux_syscall标签出。
(2)handle_head_syscall第647~679行

第648行,xnsched_root_p 是一个内联函数,用于检查当前线程是否处于 root domain(即 Linux 非实时调度域)。该函数通过调用 xnthread_test_state 来测试当前线程的状态标志位 XNROOT,从而判断线程是否运行在 root domain。当前场景下,task_struct中的ipipe_flags没有TIP_HEAD标记,肯定是运行在root domain,此处判断成立,跳转到第655行。
第655行,返回KEVENT_PROPAGATE(0)到第二步中的__ipipe_notify_syscall第1232行。
第四步,返回KEVENT_PROPAGATE到__ipipe_notify_syscall第1232行

第1232行,ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。
第1235行,因为在第1232行ipipe_syscall_hook执行过程中,当前domain一直是ipd所指向的head domain(第二步中初始化),所以第1235行条件不成立,跳转到第1239行。
第1239行,设置当前domain为this_domain即root domain(第二步中初始化)。
第1242行,根据1239行,判断条件成立,走到第1243行。
第1243行,条件1(ipd != ipipe_root_domain)成立;条件2(ret == 0)成立,因为ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。所以跳转到第1244行。
第1244~1245行,设置ipd为root domain,通过goto next跳转到第1226行。
第1229行,设置当前domain为root domain。
第1232行,进入ipipe_syscall_hook之后,因为当前domain为root domain,所以指向handle_root_syscall函数。
第五步,handle_root_syscall函数

第699行,使用__xn_syscall_p判断系统调用号是否为Xenomai系统调用号。原理参考《5.2.3 内核层:Xenomai系统调用的流程》。当前是Linux系统调用,跳转到701行。
第701行,返回KEVENT_PROPAGATE(0)到__ipipe_notify_syscall第1232行。
第六步,返回KEVENT_PROPAGATE(0)到__ipipe_notify_syscall第1232行

第1232行,ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。
第1242行,根据1239行,判断条件成立,走到第1243行。
第1243行,条件1(ipd != ipipe_root_domain)不成立;条件2(ret == 0)成立,因为ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。所以不可能走到1245行执行goto next,至此next/go next循环结束。
第1259行,KEVENT_PROPAGATE(0)到ipipe_handle_syscall第1200行。
第七步,KEVENT_PROPAGATE(0)到ipipe_handle_syscall第1200行

第1204行,因为ret等于KEVENT_PROPAGATE(0),所以跳转到第1208行,返回0到el0_svc_common第110行。
第八步,返回0到el0_svc_common第110行

第110行,从ipipe_handle_syscall返回0.
第131行,通过invoke_syscall调用Linux原生系统调用read的系统调用函数__arm64_sys_read! 具体参考《5.1.2 内核层:ARM64 Linux系统调用的流程》。
至此,完成了此场景的分析!
5.4.6 场景5之Linux线程调用Xenomai系统调用bind
本文耗时1周才梳理清楚,读者估计至少需要2小时能搞清楚流程,一定要耐心一步一步走完流程。
参考《5.2.2 用户层:用latency演示Xenomai系统调用》,latency 是 Xenomai/Cobalt 实时内核提供的一个工具,用于测量系统的实时性能,特别是上下文切换和中断响应的延迟。它启动后,会新建一个display线程用于打印信息;会新建一个sampling线程用于延迟采样。
通过条件断点,在ipipe_handle_syscall第1200行处,Linux进程运行在Root domain,调用Xenomai系统调用bind,完成对应的cobalt process的创建。
第一步:ipipe_handle_syscall

第1186行,条件1(nr >= nr_syscalls)成立,因为从用户层传下来的bind系统调用号0x10000000远大于Linux自身的系统调用数量nr_syscalls(294),具体参考《5.2 ARM64 Xenomai系统调用》。条件2(local_flags & _TIP_HEAD)不成立,因为local_flags即ti->ipipe_flags,值为0。参考《5.3.2 ipipe_flags之TIP_HEAD》,_TIP_HEAD代表线程当前运行在head domain,很明显在当前场景下是不可能的。条件1和条件2是逻辑与的关系,所以第1186行的if判断不成立,不会走到快速路径ipipe_fastcall_hook。
第1199行,条件1(local_flags & _TIP_NOTIFY)明显不成立,因为local_flags即ti->ipipe_flags,值为0。参考《5.3.3 ipipe_flags之TIP_NOTIFY》,只要是绑定到xenomai core的进程或线程,一定有TIP_NOTIFY标记!条件2(nr >= nr_syscalls)成立,上面已经分析了。条件1和条件2是逻辑或的关系,所以第1199行的if判断成立,走到慢速路径__ipipe_notify_syscall。
第二步,__ipipe_notify_syscall

第1224行,通过__ipipe_current_domain获取当前所在的domain,存入变量caller_domain和this domain。从左上角的local变量的值能够确认,当前所在domain是ipipe_root即root domain。
第1225行,设置变量ipd为ipipe_head_domain即head domain。
第1226行,此处定义了标签next:,在不同的场景下可能会通过goto next跳转过来,形成循环。
第1227行,通过ipd(head domain)获取当前cpu对应的struct ipipe_percpu_domain_data head指针,存入p。
第1228行,参考《5.3.3 ipipe_flags之TIP_NOTIFY》,此处判断必然成立。
第1229行,设置当前cpu的上下文struct ipipe_percpu_data中的curr指向存在p中保存的ipipe_percpu_domain_data head指针。
第1232行,调用ipipe_syscall_hook,在图中也展示了此函数的定义内容。在ipipe_syscall_hook中,如果判断当前是secondary domain(即root domain)则调用handle_root_syscall。而因为在__ipipe_notify_syscall的第1229行已经把当前domain设置为head domain,所以ipipe_syscall_hook最终会调用handle_head_syscall。
第三步,handle_head_syscall
handle_head_syscall函数比较长,分成两个部分来解释
(1)handle_head_syscall第483~第513行

第498行,通过__xn_syscall得到bind在xenomai中的系统调用号0。具体原理参考《5.2.3 内核层:Xenomai系统调用的流程》。
第512行,从Xenomai系统调用表中,找出bind系统调用的CoBaLt_bind函数指针。
第513行,从Xenomai系统模式表中,找出bind系统调用的调用模式。参考《5.2.4 内核层:Xenomai系统调用的定义》,在kernel/xenomai/posix/syscall_entries.h找到bind系统调用模式为__COBALT_MODE(bind, lostage) ,代表必须在Linux domain中运行。
kernel/xenomai/posix/syscall.c:
/* Syscall must run into the Linux domain. */
#define __xn_exec_lostage 0x1
(2)handle_head_syscall第551~第557行

第558行,因为bind系统调用模式为__COBALT_MODE(bind, lostage),条件成立。
第562行,caller_is_relaxed是true,所有会跳转到第557行。
第577行,返回KEVENT_PROPAGATE。
这里非常有意思,handle_head_syscall并没有调用CoBaLt_bind系统调用函数,返回KEVENT_PROPAGATE(0)到第二步中的__ipipe_notify_syscall第1232行。
第四步,返回KEVENT_PROPAGATE到__ipipe_notify_syscall第1232行

第1232行,ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。
第1235行,因为在第1232行ipipe_syscall_hook执行过程中,当前domain一直是ipd所指向的head domain(第二步中初始化),所以第1235行条件不成立,跳转到第1239行。
第1239行,设置当前domain为this_domain即root domain(第二步中初始化)。
第1242行,根据1239行,判断条件成立,走到第1243行。
第1243行,条件1(ipd != ipipe_root_domain)成立;条件2(ret == 0)成立,因为ipipe_syscall_hook返回KEVENT_PROPAGATE(0)。所以跳转到第1244行。
第1244~1245行,设置ipd为root domain,通过goto next跳转到第1226行。
第1229行,设置当前domain为root domain。
第1232行,进入ipipe_syscall_hook之后,因为当前domain为root domain,所以指向handle_root_syscall函数。
第五步,handle_root_syscall函数
(1)第712~713行

第712行,handler指向系统调用函数CoBaLt_bind
第713行,sysflags的值为1,因为bind系统调用模式为__COBALT_MODE(bind, lostage)。
(2)第746~791行

第746行,struct task_struct *p 指向当前进程的task_struct结构体。
第747行,得到传递给系统调用bind的参数,存入args。
第749行,调用系统调用CoBaLt_bind。
第791行,return KEVENT_STOP(1)到第二步中的__ipipe_notify_syscall第1232行。
第六步,返回KEVENT_STOP到__ipipe_notify_syscall第1232行

第1232行,ipipe_syscall_hook返回KEVENT_STOP(1)。
第1242行,根据1239行,判断条件成立,走到第1243行。
第1243行,条件1(ipd != ipipe_root_domain)不成立;条件2(ret == 0)不成立,因为ipipe_syscall_hook返回KEVENT_STOP(1)。所以不可能走到1245行执行goto next,至此next/go next循环结束。
第1259行,返回KEVENT_STOP(1)到ipipe_handle_syscall第1200行。
第七步,返回KEVENT_STOP(1)到ipipe_handle_syscall第1200行

第1204行,因为ret等于KEVENT_STOP(1),所以跳转到第1205行,返回-1到el0_svc_common第110行。
第八步,返回-1到el0_svc_common第110行

第111行,因为ret等于-1,条件成立,最终在第117行返回,完成系统调用!
第131行,此行完全没有机会也没有必要执行了!
总结:完全没有想到Xenomai系统调用bind的调用路径这么复杂,断断续续看了一周才算把流程整理出来。
1154

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



