当前位置: 技术问答>linux和unix
shell程序、fork()函数、execve()函数之间什么关系?
来源: 互联网 发布时间:2016-05-13
本文导语: 目前在看linux内核代码方面的东西,目前主要是文件系统FS这一块的内容。 在exec.c源码文件中,有一个函数 do_execve() 这个函数是会被 “系统调用”函数 sys_execve()会调用的,而sys_execve()是某进程通过“中断调用过程...
目前在看linux内核代码方面的东西,目前主要是文件系统FS这一块的内容。
在exec.c源码文件中,有一个函数 do_execve() 这个函数是会被 “系统调用”函数 sys_execve()会调用的,而sys_execve()是某进程通过“中断调用过程”(int 0x80, 功能号为__NR__EXECVE)被调用的,即sys_execve()函数的实现是直接通过汇编写的,如下:
####这是 sys_execve() 系统调用。取中断调用程序的代码指针作为参数调用C函数(do_execve())
[本人附加说明:sys_execve()被编译中汇编后,在汇编代码中对应的该函数“入口地址”就是指 标号“_sys_execve”处的地址]
# do_execve()在(fs/exec.c,182)。
.align 2
_sys_execve:
lea EIP(%esp),%eax
pushl %eax
call _do_execve
addl $4,%esp # 丢弃调用时压入栈的EIP值
ret
现在问题是,在linux下,如果要执行一个特定的可执行程序(假如是 hello 可执行程序),我们一般会在命令行中直接输入 hello 后回车,这时命令行所在的进程(即父进程,这个父进程一般应该是 /bin/sh,也可能是别的shell程序,如csh,bash等,但这里就以 sh 为例),然后这个父进程会通过fork()函数“创建”出一个子进程(假如以B表示该子进程吧)来,然后在B中执行 hello 这个程序(即子进程会调用execve()函数加载 hello 程序,然后在子进程空间中“转向”去执行这个程序),这样子进程B就是hello程序的进程了。
这里就有以下问题:
1、sh本身是可执行程序吗?(很多地方说,sh是解释程序)如果是解释程序?sh有源码吗?或是说 sh会解释命令行中的参数信息吧(因为 do_execve()中的参数莫名其妙地被传入了)。 sh做了些什么工作呢?
2、子进程B是在 sh 程序内部被 fork() 出来的吗?也就是说,sh程序会调用 fork() 函数吗?还做了其他什么工作呢?
3、子进程B在执行过程中,会在什么时候调用“系统调用函数”sys_execve()呢? (调用方式可能是通过“系统中断调用”int 0x80,功能号为__NR_execve (值为11)也可能是直接通过 execve() 而“转向”调用“系统中断调用”)
4、init()函数中fork()出了一个子进程,该子进程负责execve() sh解释程序,即sh程序也是通过 execve() 函数完成被加载及运行的,sh一直在运行,所以我们可以看到命令行界面的。
init()程序中执行sh的代码如下所示:
[说明:前面的省略了]
// 关闭了句柄0(stdin),以只读方式打开/etc/rc 文件,并执行/bin/sh 程序,所带参数和
// 环境变量分别由argv_rc 和envp_rc 数组给出。参见后面的描述。
if (!(pid = fork ()))
{
close (0);
if (open ("/etc/rc", O_RDONLY, 0))
_exit (1);// 如果打开文件失败,则退出(/lib/_exit.c,10)。
execve ("/bin/sh", argv_rc, envp_rc); // 装入/bin/sh 程序并执行。
_exit (2); // 若execve()执行失败则退出(出错码2,“文件或目录不存在”)。
}
[后面省略了]
即/bin/sh这个解释程序一直在运行着,然后等待着用户在命令行中输入信息。
假如就是 hello 程序,这样就回到了前面的问题了。
赵博士的《linux内核完全剖析》一书中在exec.c程序中有这样的描述:
“当一个程序使用fork()函数创建了一个子进程时,通过会在该子进程中调用exec()函数簇之一以加载执行另一个新程序。”
这里的“exec()函数簇之一”应该就是指:execve() 函数了,而这个execve()函数是通过宏定义来实现的,宏定义中就是
通过汇编完成“系统中断调用”,对应的中断向量号是0x80,采用的调用功能号是 __NR__EXECVE ,代码如下:
通过宏来完成对execve()函数的定义:
_syscall3 (int, execve, const char *, file, char **, argv, char **, envp)
#define _syscall3(type,name,atype,a,btype,b,ctype,c)
type name(atype a,btype b,ctype c)
{
long __res;
__asm__ volatile ( "int $0x80"
: "=a" (__res)
: "" (__NR_##name), "b" ((long)(a)), "c" ((long)(b)), "d" ((long)(c)));
if (__res>=0)
return (type) __res;
errno=-__res;
return -1;
}
所以调用 execve() 函数时,就会调用用C声明的sys_execve()函数,而sys_execve()函数的实现是直接通过汇编写的(开头部分)。
在exec.c源码文件中,有一个函数 do_execve() 这个函数是会被 “系统调用”函数 sys_execve()会调用的,而sys_execve()是某进程通过“中断调用过程”(int 0x80, 功能号为__NR__EXECVE)被调用的,即sys_execve()函数的实现是直接通过汇编写的,如下:
####这是 sys_execve() 系统调用。取中断调用程序的代码指针作为参数调用C函数(do_execve())
[本人附加说明:sys_execve()被编译中汇编后,在汇编代码中对应的该函数“入口地址”就是指 标号“_sys_execve”处的地址]
# do_execve()在(fs/exec.c,182)。
.align 2
_sys_execve:
lea EIP(%esp),%eax
pushl %eax
call _do_execve
addl $4,%esp # 丢弃调用时压入栈的EIP值
ret
现在问题是,在linux下,如果要执行一个特定的可执行程序(假如是 hello 可执行程序),我们一般会在命令行中直接输入 hello 后回车,这时命令行所在的进程(即父进程,这个父进程一般应该是 /bin/sh,也可能是别的shell程序,如csh,bash等,但这里就以 sh 为例),然后这个父进程会通过fork()函数“创建”出一个子进程(假如以B表示该子进程吧)来,然后在B中执行 hello 这个程序(即子进程会调用execve()函数加载 hello 程序,然后在子进程空间中“转向”去执行这个程序),这样子进程B就是hello程序的进程了。
这里就有以下问题:
1、sh本身是可执行程序吗?(很多地方说,sh是解释程序)如果是解释程序?sh有源码吗?或是说 sh会解释命令行中的参数信息吧(因为 do_execve()中的参数莫名其妙地被传入了)。 sh做了些什么工作呢?
2、子进程B是在 sh 程序内部被 fork() 出来的吗?也就是说,sh程序会调用 fork() 函数吗?还做了其他什么工作呢?
3、子进程B在执行过程中,会在什么时候调用“系统调用函数”sys_execve()呢? (调用方式可能是通过“系统中断调用”int 0x80,功能号为__NR_execve (值为11)也可能是直接通过 execve() 而“转向”调用“系统中断调用”)
4、init()函数中fork()出了一个子进程,该子进程负责execve() sh解释程序,即sh程序也是通过 execve() 函数完成被加载及运行的,sh一直在运行,所以我们可以看到命令行界面的。
init()程序中执行sh的代码如下所示:
[说明:前面的省略了]
// 关闭了句柄0(stdin),以只读方式打开/etc/rc 文件,并执行/bin/sh 程序,所带参数和
// 环境变量分别由argv_rc 和envp_rc 数组给出。参见后面的描述。
if (!(pid = fork ()))
{
close (0);
if (open ("/etc/rc", O_RDONLY, 0))
_exit (1);// 如果打开文件失败,则退出(/lib/_exit.c,10)。
execve ("/bin/sh", argv_rc, envp_rc); // 装入/bin/sh 程序并执行。
_exit (2); // 若execve()执行失败则退出(出错码2,“文件或目录不存在”)。
}
[后面省略了]
即/bin/sh这个解释程序一直在运行着,然后等待着用户在命令行中输入信息。
假如就是 hello 程序,这样就回到了前面的问题了。
赵博士的《linux内核完全剖析》一书中在exec.c程序中有这样的描述:
“当一个程序使用fork()函数创建了一个子进程时,通过会在该子进程中调用exec()函数簇之一以加载执行另一个新程序。”
这里的“exec()函数簇之一”应该就是指:execve() 函数了,而这个execve()函数是通过宏定义来实现的,宏定义中就是
通过汇编完成“系统中断调用”,对应的中断向量号是0x80,采用的调用功能号是 __NR__EXECVE ,代码如下:
通过宏来完成对execve()函数的定义:
_syscall3 (int, execve, const char *, file, char **, argv, char **, envp)
#define _syscall3(type,name,atype,a,btype,b,ctype,c)
type name(atype a,btype b,ctype c)
{
long __res;
__asm__ volatile ( "int $0x80"
: "=a" (__res)
: "" (__NR_##name), "b" ((long)(a)), "c" ((long)(b)), "d" ((long)(c)));
if (__res>=0)
return (type) __res;
errno=-__res;
return -1;
}
所以调用 execve() 函数时,就会调用用C声明的sys_execve()函数,而sys_execve()函数的实现是直接通过汇编写的(开头部分)。
|
我来说说:
1.sh本身是可执行程序, 和linux下其它可执行程序一样; 说他是解释程序,是因为他负责解释用户的输入,然后启动新进程来执
行用户输入的命令; sh 会解释用户的所有输入,判断他是否一个合法的命令,如果不是,会提示"找不到该命令 or 没有执行权限
等等"错误信息,如果是,那么就启动一个新进程来执行用户输入的命令,当然参数信息一起传递给execve()等系统调用. 最终进
入到内核入口函数sys_execve()等,进而执行do_execve(); 如果你要看shell的源代码,可以
去:http://www.gnu.org/software/bash/下载.
2. 当然是由sh fork()出来的, 一般来说,fork()和execve()函数族中的一个是配合使用的! fork()创建一个新进程,不过这个
是父进程(在这里就是sh)的一份copy(其实在物理上连copy都不是, 只是逻辑上是一个copy,因为内核采用写时复制技术).当调
用execve()系列函数的时候,才装入新的执行体(在你的例子中就是装入hello).
3. 子进程B在执行过程中, 只有在B的程序代码中你显示调用execve()函数,或者隐式调用了execve()系列函数(比如调用了
system()),那么才会调用到系统调用sys_execve(). 如果你在子进程B中没有这样的代码,它自己是不会无缘无故调用到sys_execve()的;
4. 首先,不明白说的init()函数是什么? init()应该是内核的一个函数, 该函数最终会启动用户空间的一个程序(通常叫做
init), 即: /sbin/init,一般来说是内核启动的第一个程序. 当然,这第一个程序你也可以通过给内核传递参数
init=/sbin/xxx 来指定为其它程序(比如指定init=/bin/sh,那么你就可以在kernel启动之后立即获得一个shell,连用户名密码
都不需要). 通常来说, 如何得到第一个shell的呢? 大致来说一般如下: init检查/etc/inittab,并根据规则执行在inittab里
面的命令,在inittab文件中,一般会有类似"1:2345:respawn:/sbin/mingetty tty1"这样的规则, minegetty中就会调
用/bin/login, /bin/login执行就会打印出输入用户名的提示. 当用户正确输入用户名和密码之后, /bin/login就会根
据/etc/passwd文件中用户名对应的shell来启动该shell.
这些内容比较多, 不是一两可以说的那么明了, 欢迎继续讨论!
good luck!
|
你需要把shell(如bash)跟shell程序(*.bsh)分清.
fork出来后,exec是子进程内调用的,父进程怎么可能去替换子进程的程序呢?这里也需要把进程跟可执行程序分清。
你的shell程序是否要运行完成才返回到shell来是看你是怎么运行的,哪果是加&运行就不需要等待,这里的区别只是父shell会不会wait子进程的退出状态的区别而已。
fork出来后,exec是子进程内调用的,父进程怎么可能去替换子进程的程序呢?这里也需要把进程跟可执行程序分清。
你的shell程序是否要运行完成才返回到shell来是看你是怎么运行的,哪果是加&运行就不需要等待,这里的区别只是父shell会不会wait子进程的退出状态的区别而已。
|
执行一条非shell内部命令如date如下
while(read command)
{
fork()
if child
{
find date where;
exec date;
}
else //father
{
wait; //wait child end;
}
}
上面就是shell执行一条命令做的事情。
while(read command)
{
fork()
if child
{
find date where;
exec date;
}
else //father
{
wait; //wait child end;
}
}
上面就是shell执行一条命令做的事情。
|
所以呢,这么看来,就有一点很是不理解:
从这个示例代码看来
while (filename is executable)
{
fork()
if child
{
exec filename
}
else //father
{
wait; //wait child end;
}
}
是不是要这么理解:
exec filename 这一代码行是子进程成功后,子进程在运行时所继续执行的代码。
wait 这一代码行就是“创建子进程”失败后,继续由父进程在运行时所执行的代码。
即上面这些代码是由不同的进程负责“在相关进程运行时”去执行相关代码的。
[
我一直理解成了,这些代码只能是由当前一个父进程来完成执行的,而其实父进程也不会执行到exec这样的代码的.
即这些代码是由多个进程“按需执行”一样。
不知道这次理解是不是又出现偏差了
]
-----------------------------------------
感觉你对fork还不是很明了,fork()有两个返回值,对父进程其返回值为子进程的pid,对子进程其返回值为0,因此通过if(fork()==0){exec filename;}else {wait;}父进程和子进程各自执行了一个操作,子进程exec了filename,而父进程wait子进程的返回状态。
从这个示例代码看来
while (filename is executable)
{
fork()
if child
{
exec filename
}
else //father
{
wait; //wait child end;
}
}
是不是要这么理解:
exec filename 这一代码行是子进程成功后,子进程在运行时所继续执行的代码。
wait 这一代码行就是“创建子进程”失败后,继续由父进程在运行时所执行的代码。
即上面这些代码是由不同的进程负责“在相关进程运行时”去执行相关代码的。
[
我一直理解成了,这些代码只能是由当前一个父进程来完成执行的,而其实父进程也不会执行到exec这样的代码的.
即这些代码是由多个进程“按需执行”一样。
不知道这次理解是不是又出现偏差了
]
-----------------------------------------
感觉你对fork还不是很明了,fork()有两个返回值,对父进程其返回值为子进程的pid,对子进程其返回值为0,因此通过if(fork()==0){exec filename;}else {wait;}父进程和子进程各自执行了一个操作,子进程exec了filename,而父进程wait子进程的返回状态。
|
sh只是一个命令集合而已 它会被shell解析 执行 实际的执行过程还是一个一个命令进行的
当然其中很多变量是由shell解析出来然后执行的
当然其中很多变量是由shell解析出来然后执行的
|
所以说你要明白进程跟执行程序的区别, "shell 即调用了 fork() 函数来创建一个子进程,然后也调用了 execve() 函数来加载并运行“待执行程序文件”"。这个exec明明就是在子进程里调用的啊. 不理解fork()?
|
你没有理解fork哦,不要以为一份代码就只是一个进程.fork后就分支了,后面根据fork的返回值分别进入了父进程跟子进程. wait是等待子进程执行结束,跟创建子进程是否成功无关,还是先去看看unix程序设计进程控制相关的内容吧
|
yes you got it
|
单从进程控制的角度看,fork后子进程复制了父进程的进程存储空间,因此在子进程exec之前两个进程的代码段是相同的,都要从if语句开始,至于是父进程先执行还是子进程先执行,这要看内核的调度算法了。
|
mark,一直没有勇气去内核。
|
up
|
mark