在HelloWorld程序中,我们可以调用libc中的getpid函数获取当前进程的进程号。HelloWorld是运行在用户空间,那么它是如何通过系统调用切换到内核空间来获取PID的呢?原来,在unistd.h中声明了函数原型,正是由于这个声明,我们才能在HelloWorld中调用getpid函数。
extern pid_t getpid(void);
很多Linux下C语言写的程序都需要包含 <unistd.h>这个文件,正是这个原因:它声明了大量的系统调用函数。所以说这个文件并不是C语言自己的,而是Linux系统的!
在libc中,getpid是用汇编语言写的,将系统调用号__NR_getpid写入寄存器R7(对于其它系统调用,如果有参数,则保存在其它的寄存器中),然后执行ARM的SWI软中断指令进入内核模式,开始执行内核空间的代码。
ENTRY(getpid) mov ip, r7 ldr r7, =__NR_getpid swi #0 mov r7, ip cmn r0, #(MAX_ERRNO + 1) bxls lr neg r0, r0 b __set_errno END(getpid)
系统调用号__NR_getpid的定义如下:
#define __NR_getpid (__NR_SYSCALL_BASE+ 20)
SWI的中断处理函数内部,会根据中断号去调用真正的sys_getpid函数,其定义在kernel/sys.c中:
/** * sys_getpid - return the thread group id of the current process * * Note, despite the name, this returns the tgid not the pid. The tgid and * the pid are identical unless CLONE_THREAD was specified on clone() in * which case the tgid is the same in all threads of the same group. * * This is SMP safe as current->tgid does not change. */ SYSCALL_DEFINE0(getpid) { return task_tgid_vnr(current); }
至此,完整的调用链条浮出水面: HelloWorld(user space) --> getpid(libc) --> ISR(SWI) --> sys_getpid(kernel)。其实是libc帮我们封装好了系统调用的接口,如果我们不用libc,而是直接用汇编操作系统调用的接口,也是可以进行系统调用的。
这里再讲一下系统调用的上下文,仍然属于进程上下文,不要因为软中断的概念而理解为中断上下文。其实可以这样理解:把swi看作一个函数调用,swi返回后,程序继续执行而返回getpid。只不过这个swi对应的函数调用具有特权权限,因此可以调用内核中的代码。