以文本方式查看主题

-  计算机科学论坛  (http://bbs.xml.org.cn/index.asp)
--  『 操作系统原理 』  (http://bbs.xml.org.cn/list.asp?boardid=63)
----  linux进程前世与今生   (http://bbs.xml.org.cn/dispbbs.asp?boardid=63&rootid=&id=25196)


--  作者: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不可以.


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
46.997ms