解析Linux中shell执行一次命令的全过程

1.  在busybox中先进入main函数

2.  根据调用号进入ash_main(也就是busybox的shell)

3.  进入cmdloop(1)中for循环

4.  在parsecmd中解析标准输入

5.  此时在控制台上输入./a_static执行(a_static为我的elf格式的应用程序)

6.  shell解析出命令退出parsecmd进入evaltree再进入evalcommand

7.  调用forkshell创建子线程,该子线程用来执行a_static,而父进程我们这边被先调用

8.  父进程进行waitpid系统调用进入wait4再进入do_wait,设置了局部变量wo->notask_error = -ECHILD;设置进程状态TASK_INTERRUPTIBLE

9.  进入do_wait_thread因为有子进程所以进入wait_consider_task设置wait_consider_task=0退回do_wait

10.  执行到标号notask处因为局部变量wo->notask_error = 0所以会进入进程调度,而父进程进入睡眠

11.  子线程被调度后进入shellexec再进入tryexec再进行系统调用execve加载用户空间

12.  arm中通过swi xxx (xxx为系统调用号)进入软中断向量,再进入vector_swi,保存硬件上下文,此时根据 lr-4 也就是swi指令的后半部

    确定系统调用号,根据sys_call_table中的调用号偏移找到入口函数这里是sys_execve并执行

13.  此时我们的用户空间和父进程一样,TTB中的页表基地址是父进程的页表基地址

14.  调用do_execve为线程申请用户空间

15.  退出do_execve,这时我们有了新的用户线性区(我们这边打印出来有四个线性区)和页表,TTB也已经指向自己的页全局目录

    线性区:

        8000 88000    代码区

        8f000 90000  数据区

           90000 92000  堆区

         be9b7000 be9d9000   栈+命令行参数+环境变量

16.  退出sys_execve,pop出内核栈中的硬件上下文

17.  执行完用户a_static程序后我们调用系统调用_exit(***)让进程进入僵尸态

18.  进入SYSCALL_DEFINE1(exit, int, error_code)再进入do_exit进入exit_notify进入do_notify_parent唤醒父进程,退回exit_notify

19.  设置current->exit_state=EXIT_ZOMBIE,退回do_exit

19.  调度schedule

20.  父进程由于被唤醒重新调度进入标号repeat执行,进入do_wait_thread进入wait_consider_task由于子进程tsk->exit_state == EXIT_ZOMBIE

    所以调用wait_task_zombie会返回正值,回到do_wait

21.  由于返回值>0进入标号end,不再进行睡眠和进程调度,直接退出返回用户态 

22.  回到用户态shell又进行了waitpid调度(不知道为什么)由于没有子进程所以不会进行进程调度直接再次返回用户态

    所以说进程发布waitpid调度 进入睡眠的必要条件是要有子进程。

 

 

posted @ 2019-11-15 16:41  根叔~  阅读(1218)  评论(0编辑  收藏  举报