-- 作者:enorm
-- 发布时间:12/9/2005 1:46:00 PM
-- linux进程前世与今生
一、process内功 (待续) 二、process的来龙去脉 Linux下任何进程都是由fork(或vfork)产生,exec函数簇调用执行,因此,要知晓process流程,需从fork和execve入手。所谓:不知来路,焉知出来!不等内堂,焉知外堂! 1、fork()之来龙去脉: 首先说明两点:1,在系统引导和初始化完成后,系统main在创建新process时,不使用用户态stack,故不可以采用调用的方式创建进程,所以, fork()被设计成了宏_syscall0的形式。2,用户调用fork()时,编译器不是简单地将fork()代码展开到程序中,而是使用GOT将 fork()重新定位后再执行。具体分析容我有空再续,亦可参考“参考资料”。 执行_syscall0(int, fork)之前,先将int &80中断调用号_NR_name入eax,调用int &80,中断结果入eax.,并赋给res,它就是fork()调用成功后的返回值。代码如下: ――――――――――――――――――――――――――――――――――― .//说明:一下嵌套汇编才用AT&T格式,linux亦支持intel格式。 //参数:int,fork //返回:成功:res;失败:-1 #define _syscall0(type,name) type name(void) { long __res; __asm__ volatile ("int $0x80" : "=a" (__res) : "0" (__NR_##name)); if (__res >= 0) \ return (type) __res; errno = -__res; return -1; ――――――――――――――――――――――――――――――――――――― int &80转入_sys_fork后,首先给新process分配id和task[i]中的位置i,然后再调用copy_process复制父进程任务数组结构,退出_sys_fork, 并返回eax中的值给_syscall0, 继续_syscall0直到return, fork()亦结束。 2、execve()之来龙去脉: 首先说明:execve采用宏_syscall13(不同于fork()),其他execve的原理基本同fork().首先_syscall13调用将 int &80中断依系统调用号转入_sys_execve,然后进入_do_execve()直到返回_sys_execve。在退出 _syscall13后, execve()亦即结束。 三、用户main()的生与死 "用户main"是指用户编写的非OS代码的任意C程序中的主函数.为什么叫"用户main"呢?因为OS也有一个,他们俩的“人生轨迹”可不一样哟,所以姑且取个名字,以示区别吧.既非用户,是名用户!! 1. main之"生". 为了main的诞生,系统是如何“十月怀胎”的呢? 当用户在shell下执行main函数,shell根据输入的函数名fork出新process,并调用execve启动该process.此时,这个 process是作为sh的子process运行,而shell作为父process在等待子process结束后再运行.当shell解析出main函数后,main还是立即启动真正的main(),因为: 从ELF Header: ...... Entry point address: 0x8048244 Start of program headers: 52 (bytes into file) Start of section headers: 16600 (bytes into file) ...... 知道main的入口地址是:0x8048244. 而(gdb) disass main Dump of assembler code for function main: 0x080482f9 <main+0>: push %ebp 0x080482fa <main+1>: mov %esp,%ebp 0x080482fc <main+3>: sub $0x8,%esp 0x080482ff <main+6>: and $0xfffffff0,%esp 0x08048302 <main+9>: mov $0x0,%eax 0x08048307 <main+14>: sub %eax,%esp 0x08048309 <main+16>: call 0x80482f4 <f> 0x0804830e <main+21>: mov $0x0,%eax 0x08048313 <main+26>: leave 0x08048314 <main+27>: ret (gdb) disass 0x8048244 Dump of assembler code for function _start: 0x08048244 <_start+0>: xor %ebp,%ebp 0x08048246 <_start+2>: pop %esi 0x08048247 <_start+3>: mov %esp,%ecx 0x08048249 <_start+5>: and $0xfffffff0,%esp 0x0804824c <_start+8>: push %eax 0x0804824d <_start+9>: push %esp 0x0804824e <_start+10>: push %edx 0x0804824f <_start+11>: push $0x8048348 0x08048254 <_start+16>: push $0x8048318 0x08048259 <_start+21>: push %ecx 0x0804825a <_start+22>: push %esi 0x0804825b <_start+23>: push $0x80482f9 0x08048260 <_start+28>: call 0x8048234 <__libc_start_main> 0x08048265 <_start+33>: hlt 知:main()的地址是:0x080482f9.因此.程序加载器为了将IP定位到地址0x080482f9。所以内核将 控制权交给动态链接器的入口后,先调用_dl_start函数获得真实的程序入口,获得_start的入口,即”Entry point address: 0x8048244” 转入_start例程,该程序压入一些参数到堆栈,就直接调用_libc_start_main函数。这时再"变态"(bt)一下, gdb) bt ...... #1 0x08048355 in main () at main.c:15 #2 0x42015574 in __libc_start_main () from /lib/tls/libc.so.6 欧!main()出来了,__libc_start_main ()距离它一步之遥了,曙光在前! libc_start_main.该函数注册了动态连接器解析器后,才进入main()。千呼万唤始出来!详细分析见参考资料. 为什么一个用户main()函数,会经过如此复杂的程序呢?从kernel和用户交互层面看,因为在执行用户main函数时,需要用户到内核再到用户的切换,用户到kenerl的切换时,就需要libc库了.也就是上面的步骤了.这姑且也当做一种权益的理解吧.真是"曲经通幽"! 2.main之"死" 当main over后, 系统又魂归何处. 在编写代码时,main的结束通常用两种方式,下面分别说明. 一种是exit(0).代码如下: //exit(0) 0x08048334 <main+16>: sub $0xc,%esp 0x08048337 <main+19>: push $0x0 0x08048339 <main+21>: call 0x8048264 <exit> End of assembler dump. 这种方式是通过系统调用,结束该process. 操作系统接管cpu,父process运行. 另外一种是return. //return 0x0804830e <main+21>: mov $0x0,%eax 0x08048313 <main+26>: leave 0x08048314 <main+27>: ret End of assembler dump. 这种方式是借助IP寄存器退出程序,那到底退出到哪儿去了呢?我想应该是libc_start_main吧!查libc_start_main ()和exit的原代码,噢!果真如此! 跟踪试试看: //main()原代码 11 void main() 12 { 13 //fork(); 14 //exit(0); 15 f(); 16 printf("main over!\n"); 17 return; 18 } 19 20 //main()汇编代码 ...... 0x0804835d <main+29>: call 0x8048268 <printf> 0x08048362 <main+34>: add $0x10,%esp 0x08048365 <main+37>: leave 0x08048366 <main+38>: ret End of assembler dump. (gdb) b 17 Breakpoint 5 at 0x8048365: file main.c, line 17. (gdb) b 18 Note: breakpoint 5 also set at pc 0x8048365. Breakpoint 6 at 0x8048365: file main.c, line 18. (gdb) next main over! Breakpoint 5, main () at main.c:18 18 } (gdb) i reg eip eip 0x8048365 0x8048365 (gdb) next 0x42015574 in __libc_start_main () from /lib/tls/libc.so.6 噢,当leave后,再next时,eip值为0x42015574.这又到了libc_start_main中了! 因此,return可以用来结束子程序.而exit不可以.
|