Chinaunix首页 | 论坛 | 博客
  • 博客访问: 135168
  • 博文数量: 28
  • 博客积分: 527
  • 博客等级: 中士
  • 技术积分: 367
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-09 17:05
个人简介

运维开发工程师。致力于网络,WEB应用服务,Linux系统运维。方向:操作系统,监控,自动化

文章分类

全部博文(28)

文章存档

2013年(12)

2012年(16)

分类: Python/Ruby

2012-12-06 21:15:18

导言
本节就shell子进程(subprocess)以及子SHELL(subshell)的基础概念和注意点加以介绍,配以实例,进行说明。将详细讨论如下问题:
  1. subprocess和subshell是什么
  2. subprocess的产生过程是什么
  3. 什么情况下会产生subprocess和subshell
  4. shell编程中,subshell需要注意些什么
  5. 如何管理subprocess

subprocess和subshell是什么
子进程(subporcess)是从父子进程的概念出发的。unix操作系统的进程从init进程开始,经过不断fork-exec“繁衍”,形成了树状的父子进程结构。每个进程均有其对应的父进程(0进程不在讨论范畴内),就算是由于父进程先行结束导致的孤儿进程,也会被init(pid=1)领养,使其父进程ID为1。
子SHELL,顾名思义,就是由“当前shell进程”创建的一个子进程。因此,subshell概念是subprocess的子集,一个subshell一定是个subprocess。


subprocess的产生过程
事实上,所有进程的创建,都可视为子进程创建过程。unix操作系统进程的创建,基本可以归结为fork-exec的模式,即是:
  1. 通过fork创建子进程环境,
  2. 通过exec加载并执行进程代码。
在shell环境中,即是:
  1. 读取命令:读取用户由键盘输入的命令行;
  2. 解析:以命令名作为文件名,并将其它参数改造为系统调用execve( )内部处理所要求的形式;
  3. fork:当前shell fork()出一个子进程(即子shell),此时该子shell是父shell的一个副本;
  4. 父shell等待:终端进程本身用系统调用wait4( )来等待子进程完成(如果是后台命令&,则不等待,立即返回1);
  5. 子shell exec():在subshell里,根据path指定的目录列表里的目录,找到外部命令command;以找到的命令command取代(exec)当前shell程序并执行,此时父shell等待subprocess工作完成;
  6. 命令完成后,向父进程(终端进程)报告,此时父进程醒来,在做必要的判别等工作后,回到(1),父shell再取下一条语句执行,或等待用户输入下一条命令;
图示如下:



什么情况下会产生subshell
&,提交后台作业
If a command is terminated by the control operator `&', the shell executes the command asynchronously in a subshell.

管道
Each command in a pipeline is executed in its own subshell

括号命令列表 ()操作符
Placing a list of commands between parentheses causes a subshell environment to be created

执行外部脚本、程序
When Bash finds such a file while searching the `$PATH' for a command, it spawns a subshell to execute it.  In other words, executing filename ARGUMENTS is equivalent to executing bash filename ARGUMENTS


示例
1.shell在子shell中执行外部命令
比如在当前网络终端(通过ssh),键入ps -ef|grep work,看看进程树是如何的:
  1. $ echo $$        #得到当前bash的pid 6010
  2. $ pstree -n -a | less    #查看进程树

  3. init(1)-+-migration/0(2)
  4.         |-ksoftirqd/0(3)
  5. ...skipping...
  6.         |-sshd(2679)---sshd(5997)---sshd(6009)---bash(6010)-+-pstree(9378)
  7.         |       `-less(9379)
从结果可以看到,所有进程由init派生而来,2679为sshd系统服务;5997和6009为sshd为work当前终端分配的一个session;6010为基于这个session的、为work用户提供交互操作的shell进程;由于包含管道命令,9378和9379为6010的子进程,即6010(bash)进程spawn了两次。
其中,当键入pstree | less 时,shell先分析pstree是否为非内建命令或别名,结果是外部命令,需要在子进程中执行之,故另启动一个进程(9378)去执行pstree命令;同样,less也按照上述方法执行。

2. 例1的延续
再来看下如果调用sh脚本,系统如何表现:
脚本示例:
  1. #! /bin/bash
  2. ping 127.0.0.1 | tail -f | grep time &> /dev/null
  3. ---------------------------------------
  4. $ sh test.sh
  5. $ pstree -n -a | less    #在另一个terminal查看进程树
        |-sshd(2679)-+-sshd(5139)---sshd(5143)---bash(5144)---sh(10252)-+-ping(10253)
        |            |                                                  |-tail(10254)
        |            |                                                  `-grep(10255)
---------------------------------------
root      5139  2679  0 08:52 ?        00:00:00 sshd: work [priv]
work      5143  5139  0 08:52 ?        00:00:00 sshd: work@pts/0 
work      5144  5143  0 08:52 pts/0    00:00:00 -bash
work     10252  5144  0 09:07 pts/0    00:00:00 sh test.sh
work     10253 10252  0 09:07 pts/0    00:00:00 ping 127.0.0.1
work     10254 10252  0 09:07 pts/0    00:00:00 tail -f
work     10255 10252  0 09:07 pts/0    00:00:00 grep tome

可见,当sh test.sh的时候,当前bash6010另启动一个subshell(10252)去执行test.sh内的语句,之后的语句可视作把10252作为“执行test.sh的主干道”,ping命令作为10252的子进程(10253)执行,直到test.sh最后一句执行完毕,10252才完成他的工作,结束并返回给交互式bash6010。


在本体shell中执行脚本
有两个方法可以使shell在本体进程中执行脚本
1. source
一次性读取脚本内的所有shell语句,并依次顺序执行,不退出。
  1. echo $$        #21502 终端shell进程
  2. ---------------------------------------
  3. #!/bin/bash #script.sh
  4. ping 127.0.0.1 | tail -f | grep time
  5. ---------------------------------------
  6. source script.sh
  7. ---------------------------------------
  8. ps -ef|grep work
  9. #output
  10. work 5404 5403 0 Dec07 pts/1 00:00:00 -bash
  11. root 21497 2679 0 10:16 ? 00:00:00 sshd: work [priv]
  12. work 21501 21497 0 10:16 ? 00:00:00 sshd: work@pts/2
  13. work 21502 21501 0 10:16 pts/2 00:00:00 -bash
  14. work 22735 21502 0 10:20 pts/2 00:00:00 ping 127.0.0.1
  15. work 22736 21502 0 10:20 pts/2 00:00:00 tail -f
  16. work 22737 21502 0 10:20 pts/2 00:00:00 grep time
可见,脚本里的命令都从21502(bash)中fork出来执行。

2. exec
相当于用"bash script"替换了本体进程,当在本地进程执行完毕后,已经没有shell程序了,退出。
  1. #!/bin/bash
  2. ping 127.0.0.1 -c 10
  3. ---------------------------------------
  4. exec ./test.sh
  5. PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
  6. 64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.010 ms
  7. ...
  8. 64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.005 ms
  9. --- 127.0.0.1 ping statistics ---
  10. 10 packets transmitted, 10 received, 0% packet loss, time 8999ms
  11. rtt min/avg/max/mdev = 0.004/0.006/0.010/0.002 ms, pipe 2
  12. Connection to yf-adrc-ct00.yf01 closed.
可见,用exec执行的话,脚本结束即退出当前终端。


父子进程在编程中需要注意些什么
了解了进程fork的关系,就能明白为什么子进程不能改父进程的变量。
父子进程的运行环境、内存空间都是独立的,父进程的环境变量会传递到子进程里,子进程对任何变量的变更不会影响父进程。
  1. $ echo $$ #父进程7182
  2. 7182
  3. $ name=father #定义name变量
  4. $ export name #使其环境变量
  5. ---------------------------------------
  6. $ bash #产生一个子进程
  7. $ echo $$ #子进程8303
  8. 8303
  9. $ echo $name #子进程得到了name
  10. father
  11. $ name=son #变更name=son
  12. $ exit #回到父进程
  13. ---------------------------------------
  14. $ echo $name #父进程的name不变,还是"father"
  15. $ father
结合子进程产生的条件,在变成中应该避免在子进程中企图改变父进程的变量值。


参考资料
http://blog.csdn.net/yjz0065/article/details/1190879
http://blog.csdn.net/sosodream/article/details/5683515
阅读(2430) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~