免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5976 | 回复: 2
打印 上一主题 下一主题

关于子进程、子shell的疑问 [复制链接]

论坛徽章:
1
双子座
日期:2013-10-17 00:46:45
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-08-17 21:47 |只看该作者 |倒序浏览
关于子进程、子shell的疑问:

1、 子 shell 和 子进程 究竟有什么区别和联系?还是两者等价?

2、他们的原始定义是怎么描述的呢?哪里可以查到?

谢谢~

论坛徽章:
8
摩羯座
日期:2014-11-26 18:59:452015亚冠之浦和红钻
日期:2015-06-23 19:10:532015亚冠之西悉尼流浪者
日期:2015-08-21 08:40:5815-16赛季CBA联赛之山东
日期:2016-01-31 18:25:0515-16赛季CBA联赛之四川
日期:2016-02-16 16:08:30程序设计版块每日发帖之星
日期:2016-06-29 06:20:002017金鸡报晓
日期:2017-01-10 15:19:5615-16赛季CBA联赛之佛山
日期:2017-02-27 20:41:19
2 [报告]
发表于 2011-08-18 04:46 |只看该作者
子shell就是运行在子进程里的shell

论坛徽章:
15
2015年辞旧岁徽章
日期:2015-03-03 16:54:15双鱼座
日期:2015-01-15 17:29:44午马
日期:2015-01-06 17:06:51子鼠
日期:2014-11-24 10:11:13寅虎
日期:2014-08-18 07:10:55酉鸡
日期:2014-04-02 12:24:51双子座
日期:2014-04-02 12:19:44天秤座
日期:2014-03-17 11:43:36亥猪
日期:2014-03-13 08:13:51未羊
日期:2014-03-11 12:42:03白羊座
日期:2013-11-20 10:15:18CU大牛徽章
日期:2013-04-17 11:48:45
3 [报告]
发表于 2011-08-18 09:02 |只看该作者
转个贴,你先看一下吧。
http://hi.baidu.com/leejun_2005/ ... ada07cd1164e28.html

关于子 shell / 子进程 的讨论
2011-08-18 01:17
from:http://bbs.chinaunix.net/viewthr ... p;extra=&page=1
from:http://bbs.chinaunix.net/thread-1479880-2-1.html
from:http://tldp.org/LDP/abs/html/subshells.html
from:http://bbs.chinaunix.net/viewthr ... ;extra=&page=1#
from:http://bbs.chinaunix.net/thread-742295-1-1.html

例子1、
cat sleep.sh
#!/bin/sh
sleep 100
[root@rhel-oracle shell]# ps -ef | grep sleep
root      9007  8947  1 05:10 pts/1    00:00:00 /bin/sh ./sleep.sh
root      9008  9007  0 05:10 pts/1    00:00:00 sleep 100
root      9010  8947  0 05:10 pts/1    00:00:00 grep sleep
[root@rhel-oracle shell]# echo $$
8947
[root@rhel-oracle shell]#
shell命令(sleep)就是执行它的这个终端的孙shell,sh的子shell
如果是脚本执行的,那命令就是执行它的这个脚本的shell的子shell
子shell其实就是当前shell启动的一个子进程,这个子进程也就是子shell,来执行shell脚本的语句,shell脚本里面的命令,都是由这个子shell来开启子进程

例子2、
cat a
echo $$
15:40:22#~> (./a.sh)
2140
15:40:28#~> ./a.sh
3292
15:40:30#~> echo $$
1248
15:40:38#~> (echo $$)
1248
15:40:42#~>
$$不能衍生到子shell的,
[root@localhost ~]# echo $$;(echo $$);
15788
15788
要用 bash4 的一个新的内置变量 $BASHPID 可以扩展到子进程

3、关于child process/subshell 的定义:
Definition: A subshell is a child process launched by a shell (or shell script).  #http://tldp.org/LDP/abs/html/subshells.html
A subshell does not create a new instance of the shell, just a new environment. #http://bbs.chinaunix.net/thread-1479880-2-1.html
按这句话的意思来说的话,岂不是所有子进程都由shell启动的?
我刚才说过嘛,是shell运行产生了进程(init和crontab呢?)
echo a|grep a这个echo和grep是子shell
a.sh里面调用,不是子shell (错了)
If  this  execution  fails  because the file is not in executable format, and the file is not a directory, it is assumed to be a shell script, a file containing shell commands.  A subshell is spawned to execute it.



4、为啥说命令也是子shell
这个pid的ppid是刚才那个shell的pid
如果说你在终端执行了一个命令,那么这个命令是终端这个shell的子shell
如果脚本里执行了一个命令,这个命令是脚本的子shell
脚本本身也是个shell
我算是明白了
这其实涉及到clone和execve两个调用
shell中,外部命令后台调用时,会先fork一个子shell,然后用exec去执行这个命令,那么这个子shell也就消亡了,从ps中是看不到这个子shell存在的,因为被替换了
也不能说消亡,他所在进程依然存在
subshell存在的时间也就是fork之后exec之前?
嗯,我是这么想的
是的


5、运行下面三条命令,分析结果
a(){ sleep 30;}
a &
pstree -pa $$  
用rh,bash环境讨论呵  
bash,1248
  +-bash,2452
  |   +-sleep,1672 30
  +-pstree,288 -pa 1248
&生成一个子shell,#If  a  command  is  terminated by the control operator &, the shell executes the command in the background in a subshell.
函数生成一个子shell,
所以两个

有时间再看看这个,看是不是函数产生的进程,还是命令产生的进程:
a(){ while true ;do : ;done; }
a &
pstree -pa $$
对比一下
======================================================================================================

6、不過要提醒一下:
sub shell 也有分 sub shell 跟 nested sub shell 。
sub shell a child shell executed under an other shell


7、几个知识点
1.Bash在实现pipeline(管道|)时会发起两个subshell(子shell)来运行|两边的命令,对于系统来说就是发起两个childprocess(子进程)
2.fork是产生process的唯一途径,exec*是执行程序的唯一途径
3.子进程会完全复制父进程,除了$PID与$PPID
4.fork子进程时继承父进程的进程名,在exec*执行命令时才由exec*替换为子进程对应的命令,同一进程的命令名可以由一个个exec*任意多次的改变

8、下面的脚本 get_process

    #!/bin/bash
    ps -ef|grep get_process

复制代码

运行./get_process后会得到什么结果

结果有三种

    waker     3260  3193  0 08:47 pts/1    00:00:00 /bin/bash ./get_process

复制代码

    waker     3290  3193  0 08:48 pts/1    00:00:00 /bin/bash ./get_process
    waker     3292  3290  0 08:48 pts/1    00:00:00 /bin/bash ./get_process

复制代码

    waker     3263  3193  0 08:48 pts/1    00:00:00 /bin/bash ./get_process
    waker     3265  3263  0 08:48 pts/1    00:00:00 grep get_process

复制代码

大家产生了种种解释
其实大家离真相只有0.01mm的距离
让我来提示一下
观察三种结果,都有一个共同的进程,就是PPID 是3193的 /bin/bash ./get_process
那么3193是什么
只要echo $$就可以看出3193就是我们键入 ./get_process的shell

让我们参照四个知识点来解释这个现象
首先当前shell 3193发起一个subshell来执行脚本./get_process
这个进程pid 3290 进程名 /bin/bash ./get_process
然后当它遇到管道,将发起两个子进程 3291(用来执行ps) 3192(用来执行grep),这两个进程是并行的
让我们给3192来个慢镜头

               史前时代                   fork                                       exec*(grep.*)
    pid          N/A                               3192
    进程名       N/A        /bin/bash ./get_process                  grep get_process
    说明         N/A       这个进程名继承自父进程                          exec*将进程名替换

复制代码


而用3291中的ps来观察这个进程的时候,由于两个子进程的并行,每一种状态都可能被ps看到,当然每次只能看到一种状态
所以结果会有三种情况
进程3290(/bin/bash ./get_process)
与下面三种可能的组合
1.空
2.子进程3292 进程名 /bin/bash ./get_process
3.子进程3292 进程名 grep get_process

9、子进程能传值给父进程吗?
不可以,但是可以变通下,source ./urcode.sh 或者管道、文件吧

10、命令、进程、子shell
Q1: shell如何执行“简单”命令?
A: 这里的简单命令和bash参考手册里的含义相同,形式上一般是:命令的名称加上它的参数。有三种不同的简单命令:
1.内置命令(builtin)
是shell解释程序内建的,有shell直接执行,不需要派生新的进程。有一些内部命令可以用来改变当前的shell环境,如:
cd /path
var=value
read var
export var
...

2.外部命令("external command" or "disk command"
二进制可执行文件,需要由磁盘装入内存执行。会派生新的进程,shell解释程序会调用fork自身的一个拷贝,然后用exec系列函数来执行外部命令,然后外部命令就取代了先前fork的子shell。

3.shell脚本(script)
shell解释程序会fork+exec执行这个脚本命令,在exec调用中内核会检查脚本的第一行(如:#!/bin/sh),找到用来执行脚本的解释程序,然后装入这个解释程序,由它解释执行脚本程序。解释程序可能有很多种,各种shell(Bourne shell,Korn shell cshell,rc及其变体ash,dash,bash,zshell,pdksh,tcsh,es...),awk,tcl/tk,expect,perl,python,等等。在此解释程序显然是当前shell的子进程。如果这个解释程序与当前使用的shell是同一种shell,比如都是bash,那么它就是当前shell的子shell,脚本中的命令都是在子shell环境中执行的,不会影响当前shell的环境。


Q2: shell脚本是否作为单独的一个进程执行?
A: 不是,shell脚本本身不能作为一个进程。如上面讲的,shell脚本由一个shell解释程序来解释、运行其中的命令。这个shell解释程序是单独的一个进程,脚本中的外部命令也都作为独立进程依次被运行。这也就是为什么ps不能找到正在运行的脚本的名字的原因了。作为一个替代方案,你可以这样调用脚本:
sh script-name
这时shell解释程序“sh”作为一个外部命令被显式地调用,而script-name作为该命令的命令行参数可以被我们ps到。
另外,如果你的系统上有pidof命令可用,它倒是可以找出shell脚本进程(实际上应该是执行shell脚本的子shell进程)的进程ID:
pidof -x script-name


Q3: shell何时在子shell中执行命令?
A: 在此我们主要讨论Bourne shell及其兼容shell。在许多情况下shell会在子shell中执行命令:
1.(...)结构
小括号内的命令会在一个子shell环境中执行,命令执行的结果不会影响当前的shell环境。需要注意是此时变量$$会显示当前shell的进程id,而不是子shell的进程id。
参考:
{...;}结构中的命令在当前shell中执行,(内部)命令执行的结果会影响当前的shell环境。

2.后台执行或异步执行
command&
命令由一个子shell在后台执行,当前shell立即取得控制等候用户输入。后台命令和当前shell的执行是并行的,但没有互相的依赖、等待关系,所以是异步的并行。

3.命令替换
`command`(Bourn shell及兼容shell/csh)
$(command)(在ksh/bash/zsh中可用)
将command命令执行的标准输出代换到当前的命令行。command在子shell环境中执行,结果不会影响当前的shell环境。

4.管道(不同的shell处理不同)
cmd1|cmd2
cmd1和cmd2并行执行,并且相互有依赖关系,cmd2的标准输入来自cmd1的标准输出,二者是“同步”的。
对管道的处理不同的shell实现的方式不同。
在linux环境下大多数shell(bash/pdksh/ash/dash等,除了zshell例外)都将管道中所有的命令在子shell环境中执行,命令执行的结果不会影响当前的shell环境。
Korn shell的较新的版本(ksh93以后)比较特殊,管道最后一级的命令是在当前shell执行的。这是一个feature而非BUG,在POSIX标准中也是允许的。这样就使下面的命令结构成为可能:
command|read var
由于read var(read是一个内部命令)在当前shell中执行,var的值在当前shell就是可用的。
反之bash/pdksh/ash/dash中read var在子shell环境中执行,var读到的值无法传递到当前shell,所以变量var无法取得期望的值。类似这样的问题在各种论坛和news group中经常被问到。个人认为command|read var的结构很清晰,并且合乎逻辑,所以我认为Korn shell的这个feature很不错。可惜不是所有的shell都是这样实现的。如开源的pdksh就是在子shell执行管道的每一级命令。
Korn shell对管道的处理还有一个特殊的地方,就是管道如果在后台执行的话,管道前面的命令会由最后一级的命令派生,而不是由当前shell派生出来。据说Bourne shell也有这个特点(标准的Bourne shell没有测试环境,感兴趣的朋友有条件的可以自行验证)。但是他们的开源模仿者,pdksh和ash却不是这样处理。
最特殊的是zshell,比较新的zshell实现(好像至少3.0.5以上)会在当前shell中执行管道中的每一级命令,不仅仅是最后一条。每一条命令都由当前shell派生,在后台执行时也是一样。可见在子sehll中执行管道命令并不是不得已的做法,大概只是因为实现上比较方便或者这样的处理已经成为unix的传统之一了吧。
让我们总结一下,不同的shell对管道命令的处理可能不同。有的shell中command|read var这样的结构是ok的,但我们的代码出于兼容性的缘故不能依赖这一点,最好能避免类似的代码。

5.进程替换(仅bash/zsh中,非POSIX兼容)
<(...)
>(...)
与管道有点类似,例子:cmd1 <(cmd2) >(cmd3), cmd1, cmd2, cmd3的执行是同步并行的。
<(command)形式可以用在任何命令行中需要填写输入文件名的地方,command的标准输出会被该命令当作一个输入文件读入。
>(command)形式可以用在任何命令行中需要填写输出文件的地方,该命令的输出会被command作为标准输入读入。
两种形式中的command都在子shell环境中执行,结果不会影响当前的shell环境。

6.if或while命令块的输入输出重定向
在SVR4.2的Bourne shell中对此情况会fork一个子shell执行if块和while块中的命令;在linux下似乎其它的shell中都不这样处理。

7.协进程(ksh)
只有Korn shell和pdksh有协进程的机制(其它shell中可以用命名管道来模拟)。类似于普通的后台命令,协进程在后台同步运行,所以必须在子shell中运行。协进程与后台命令不同的是它要和前台进程(使用read -p和print -p)进行交互,而后者一般只是简单地异步运行。


Q4: 既然在当前shell中执行命令也会派生子shell,那么它与在子shell中执行命令又有什么区别呢?
A: 这种说法不准确。
在当前shell中执行内部命令不会派生子shell,因此有些内部命令才能够改变当前的shell执行环境。
在当前shell中执行外部命令或脚本时会派生子shell,所以这时命令的执行不会影响当前 的shell环境。注意:子shell中执行的内部命令只会改变子shell的执行环境,而不会改变当前shell(父shell)的环境。


Q5: 怎样把子shell中的变量传回父shell?
A: 例如(echo "$a" | read b不能工作,如何找到一个替代方案?下面给出一些可能的方案:
1.使用临时文件
...
#in subshell
a=100
echo "$a">tmpfile
...
#in parent
read b<tmpfile

2.使用命名管道
mkfifo pipef
(...
echo "$a" > pipef
...)
read b <pipef

3.使用coprocess(ksh)
( echo "$a" |&)
read -p b

4.使用命令替换
b=`echo "$a"`

5.使用eval命令
eval `echo "b=$a"`

6.使用here document
read b <<END
`echo "$a"`
END

7.使用here string(bash/pdksh)
read b <<<`echo "$a"`

8.不用子shell,用.命令或source命令执行脚本。
即在当前shell环境下执行脚本,没有子shell,也就没有了子shell的烦恼。

解决的方法还不止于此,其它的进程间通信手段应该也能使用,这有待于大家一起发掘了。^_^
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP