免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 10091 | 回复: 15

trap的一个问题 [复制链接]

论坛徽章:
0
发表于 2007-11-24 00:22 |显示全部楼层
今天看例外处理命令trap,有点不明白,请指点下

#! /bin/bash
sleep 20&
trap "echo sleep interrupted" 2

运行这个脚本时候,按ctrl+c后没有
显示sleep interrupted呢?
当脚本执行的时候,shell取出echo sleep interrupted放入trap表中,
当收到信号时候,shell从trap表中取出echo sleep interrupted并执行这个
命令,为什么没有显示出执行结果呢?

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
发表于 2007-11-24 00:35 |显示全部楼层
#! /bin/bash
trap "echo sleep interrupted" 2
sleep 20

论坛徽章:
3
2015年迎新春徽章
日期:2015-03-04 09:56:11数据库技术版块每日发帖之星
日期:2016-08-03 06:20:00数据库技术版块每日发帖之星
日期:2016-08-04 06:20:00
发表于 2007-11-24 14:35 |显示全部楼层
ctrl+c只可以传给前台,
trap内置命令是设置shell的,但shell在运行过程中,如果当前有前台程序在运行,shell是无法直接获得SIGINT,其原理是这样的——
trap命令是要先设置shell的信号处理(此为系统调用),然后设置等待子进程结束之后的处理,通过比方waitpid之类的系统调用得知子进程死于何种信号之手,然后再用实现trap设置好的去处理。

论坛徽章:
0
发表于 2007-11-24 17:06 |显示全部楼层
这个说法有问题吧。
ctrl+c,涉及到前台进程组,和终端设置属性有关。
其意义就是说,当按下ctrl+c时候,(前提是stty -a看到,intr=^C,很多系统默认是del),终端驱动程序检测到,并且根据这个stty的设定,给每个属于前台进程组(进程组可以有很多,但是任一时刻,只能有一个前台进程组),给该组的每个进程都发送SIGINT。
首先,要明确,一般的shell实现而言,如果是用交互式shell来运行,
为了方便问题叙述,假定我们现在的shell是bash
那么ksh -i -c "cmd"和ksh -c "cmd"
在ksh -i 模式下,cmd和ksh不是一个进程组,ksh和bash又不是一个进程组。
在ksh直接-c没有-i模式下,一般的实现而言,ksh和cmd是一个进程组,而ksh和bash不是一个进程组。
为什么是这个结果? 想想看?

这样我们可以分析,
有个x.sh文件
trap "cmd" sig
xxx
xxx

我们会sh  x.sh 或者直接x.sh
不过不论什么模式,都会有当前的登录shell启动一个子shell来执行这个x.sh。而且,该shell和x.sh中需要启动的子进程都是一个进程组(观察到这点很重要)。因为如果不是交互模式的shell来执行,则该子shell和其子进程都会属于一个进程组(除非被调用的cmd又重新设置了其组id),否则,子shell和其子shell属于不同的进程组。为什么ctrl+c会中断该子shell和其子进程呢(不论是否交互shell启动),原因就在于,当非交互式启动,他们自然是一个组,都属于同一个前台进程组。当交互式启动时候,他们不属于同一个进程组,但是请注意,一个进程只要有控制终端(ps -f时候,能看到tty不是?的就是),就能让自己参加该前台进程组(注意,参加前台进程组,不会改变自己的组id)。事实上,我们也可以看到,在交互模式启动下,子shell和其子进程不是一个组,但是他们也属于同一个前台进程组。并且该前台进程组号就是子shell的子进程的组号。因此我们可以推想,子shell,为了一直保持和其子进程在同一个前台进程组,不断的调用了tcsetpgrp,来参加其子进程所在的组。否则,按照现在unix的规则,就会发现,ctrl+c后,子shell不会终止,但是其子进程会终止!这当然不是我们想要的。

以上的阐述,综合起来就是三句话:
1,ctrl+c(如果就是终端产生SIGINT设置的话),会送到当前的前台进程组的每一个进程。
2,前台进程组里的进程,可以都是同一个组,也可以不是同一个组。
3,前台进程组是一个终端属性,同一终端,任一时刻,最多只能有一个前台进程组。

事实上,shell trap一个信号,并非是wait子进程的退出态,如果不是正常结束(128+信号数值),就调用trap。恰恰相反,是shell为自己设定了一个信号处理,因为他和子进程是同一个前台进程组,因此,可以得到从终端驱动发送到该组每个成员的一个信号(并非shell无法直接获得SIGINT),在自己收到SIGINT时候,即触发自己设定的动作。

这一点,其实可以通过一个简单的实验证实:
x.sh在trap 后,执行调用一个很小的C程序编译后的binary,该C文件实现为一直等待,捕捉SIGINT后exit(0)。这样x.sh就会认为该子进程是正常结束?按照cjaizss 的说法,就会得出,x.sh本身不会触发trap的动作。实际上,肯定会的。其实有个更简单的测试。
sh -c "trap 'echo ok' 2; sh -c \"trap 'exit 0' 2; read\""

如果ctrl+c,会出现ok。

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
发表于 2007-11-25 01:04 |显示全部楼层
原帖由 ivhb 于 2007-11-24 17:06 发表
这个说法有问题吧。
ctrl+c,涉及到前台进程组,和终端设置属性有关。
其意义就是说,当按下ctrl+c时候,(前提是stty -a看到,intr=^C,很多系统默认是del),终端驱动程序检测到,并且根据这个stty的设定, ...

说得太好啦, 学习!

论坛徽章:
3
2015年迎新春徽章
日期:2015-03-04 09:56:11数据库技术版块每日发帖之星
日期:2016-08-03 06:20:00数据库技术版块每日发帖之星
日期:2016-08-04 06:20:00
发表于 2007-11-25 01:10 |显示全部楼层
呵呵,我说过了shell的trap动作同时设置自身的信号处理和等待子进程的处理。你可以试下这个就清楚了。

  1. /*1.c*/
  2. #include <signal.h>
  3. #include <unistd.h>
  4. #include <stdio.h>
  5. void f(int s){sleep(10);}
  6. int main()
  7. {
  8.      signal(SIGINT,f);
  9.     pause();
  10. }
复制代码

gcc 1.c后
1.sh

  1. #!/bin/sh
  2. ./a.out
复制代码

执行shell,对a.out进程发个SIGINT
另外,既然shell设置了自身信号处理,比方read命令,shell跳到前台,属于前台进程组的一员了,接到信号,自然会执行trap设置动作

[ 本帖最后由 cjaizss 于 2007-11-25 01:19 编辑 ]

论坛徽章:
3
2015年迎新春徽章
日期:2015-03-04 09:56:11数据库技术版块每日发帖之星
日期:2016-08-03 06:20:00数据库技术版块每日发帖之星
日期:2016-08-04 06:20:00
发表于 2007-11-25 01:14 |显示全部楼层
如果还不能说明问题的话
[/code]
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void f(int s){printf("test\n");}
int main()
{
     signal(SIGINT,s);
     fork();
    pause();
}
[/code]
在前台运行./a.out
ctrl+c

论坛徽章:
0
发表于 2007-11-25 12:40 |显示全部楼层
谢谢各位前辈的指点,学习了!!!
:wink: :wink: :wink: :wink: :wink: :wink: :wink: :wink: :wink: :wink:
:wink: :wink: :wink: :wink: :wink: :wink: :wink: :wink: :wink: :wink:

论坛徽章:
0
发表于 2007-11-25 15:51 |显示全部楼层

回复 #7 cjaizss 的帖子

原帖由 cjaizss 于 2007-11-25 01:14 发表
如果还不能说明问题的话

#include
#include
#include
void f(int s){printf("test\n");}
int main()
{
     signal(SIGINT,s);
     fork();
    pause();
}

在前台运行./a.out
ctrl+c



首先,signal(SIGINT, s)是个笔误,应该是signal(SIGINT, f);
小问题,我们忽略。
现在我们cc -o ./a.out xx.c 得到了这个./a.out
/tmp>./a.out

好的,我们换个终端
/tmp>ps -xj
PPID   PID    PGID   SID   TTY       TPGID STAT   UID   TIME COMMAND
31875 31876 31876 31876 pts/5     1149  Ss      504    0:00 -bash
31875 32374 32374 32374 pts/6     1158  Ss      504    0:00 -bash
31876  1149  1149  31876 pts/5     1149  S+      504    0:00 ./a.out
1149   1150  1149   31876 pts/5     1149  S+      504    0:00 ./a.out


很显然,进程号=sid号的session的首领,就是我们的loginshell,这里就是红色标出的bash,其后的两个./a.out(进程号为别为
1149,1150)。TPGID就是前台进程组号。注意我们还没有执行ctrl+c,已经看到,bash这时候已经是前台进程组。而并非cjaizss 朋友在上一帖子说得,shell执行了read,跑到了前台。(再此,我妄解一下cjaizss 朋友的意思,原来的表述是“跑到前台”,我理解成“跑到前台进程组”,不知道是不是合适。因为终端上按下ctrl+c引发的动作是专门的对于“前台进程组”这个概念来说的。而不是说,只有某个进程获取了终端(读写)才能得到ctrl+c的动作。这个概念应该是不对的。可以想象,任一时刻,只有可能一个进程block/use在终端读写上。不知道这样把cjaizss 朋友的“前台”,置换成这里的“前台进程组”是不是有什么问题。希望cjaizss 可以进一步的交流澄清。)实际上我们看到,进程号31876,1149,1150都跑到了同一个前台进程组。只是前者进程组号是31876,后者1149(观察PGID列)。这就是登录bash,总是交互模式启动,所以才会导致后两者又新启动了一个进程组。需要说明的一点,一个进程是否能够接受到信号,和当前是否占据终端无关。当我们按下ctrl+c,终端驱动程序,就会得到前台进程组号,依次给该组的每个成员发送信号。导致的结果就是两个./a.out分别收到了SIGINT,打印出一句话。pause返回-1,pause返回-1。因为pause返回-1,因此整个main返回-1,因为bash是通过WEXITSTATUS来获取其子进程的返回值,这样-1的低8位就是$?的值,正好是0xFF(因为-1就是全bit全1的int)为255。和我们的实验一致。因为bash是个登录shell,设计成忽略SIGINT,因此,即便命令行显式的kill -2 $$仍然无法让其终止。当我们按下ctrl+c时候,并非该登录shell没有接受到SIGINT(只要它在前台进程组,就一定会接收到),而是因为它作为登录shell启动时候,已经设置了忽略了该信号。

题外话,如果
gcc -std=c99 x.c来编译,./a.out执行,那么ctrl+c后,$?应该是0。因为c99要求main没有明确的返回值,则要求返回0.

论坛徽章:
3
2015年迎新春徽章
日期:2015-03-04 09:56:11数据库技术版块每日发帖之星
日期:2016-08-03 06:20:00数据库技术版块每日发帖之星
日期:2016-08-04 06:20:00
发表于 2007-11-25 16:33 |显示全部楼层
呵呵,我的试验做错了,重新做了一个试验。
trap只设置了shell的信号处理,并没有去管子进程的退出状态,子进程是否是由于信号而死于非命shell是不管的。
shell进程如果是该对话期的session leader,则在运行前台进程的时候,shell进程是作为后台的。
#trap "echo test" 2
#sleep 1000
这个时候只要ctrl+c就可以看出来
然而如果写了一个shell程序,在某别的终端的shell提示符下键入比方./test.sh运行这个程序,则新建立的这个shell并非session leader,并一直在前台运行,ctrl+c对该shell是有效的。
我当初是被类似以下试验误解的:

  1. #!/bin/sh
  2. ./a.out
复制代码

  1. #include <signal.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <sys/types.h>
  5.        #include <sys/wait.h>

  6. #include <unistd.h>
  7. void f(int s)
  8. {
  9.         sleep(10);
  10.         _exit(0);
  11. }
  12. int main()
  13. {
  14.         signal(SIGINT,f);
  15.          pause();
  16.        exit(0);
  17. }
复制代码

当ctrl+c之后,等到./a.out退出后才打出test
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

SACC2021中国系统架构师大会

【数字转型 架构重塑】2021年5月20日-22日第十三届中国系统架构师大会将在云端进行网络直播。

大会为期3天的议程,涉及20+专场,近120个主题,完整迁移到线上进行网络直播对会议组织来说绝非易事;但考虑到云端会议的直播形式可以实现全国各地技术爱好者的参与,也使ITPUB作为技术共享交流平台得到更好的普及,我们决定迎难而上。
http://sacc.it168.com/


大会官网>>
  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP