解析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调度 进入睡眠的必要条件是要有子进程。