linux系统下信号详解2

信号是UNIX 系统所使用的进程通信方法中,最古老的一种。信号不但能从内核发往一个进程,也能从一个进程发往另一个进程。例如,用户在后台启动了一个要运行较长时间的程序,如果想中断其执行,可以用kill 命令把SIGTERM信号发送给这个进程,SIGTERM 将终止此进程的执行。信号还提供了向UNIX 系统进程传送软中断的简单方法。信号可以中断一个进程,而不管它正在作什么工作。由于信号的特点,所以不用它来作进程间的直接数据传送,而把它用作对非正常情况的处理。由于信号本身不能直接携带信息,这就限制了它作为一项通用的进程通信机制。

.SIGHUP信号

UNIX中进程组织结构为 session (会话)包含一个前台进程组及一个或多个后台进程组,一个进程组包含多个进程。一个session可能会有一个session首进程,而一个session首进程可能会有一个控制终端。一个进程组可能会有一个进程组首进程。进程组首进程的进程ID与该进程组ID相等。这儿是可能会有,在一定情况之下是没有的。与终端交互的进程是前台进程,否则便是后台进程。

SIGHUP会在以下3种情况下被发送给相应的进程:

1、终端关闭时,该信号被发送到session首进程以及作为job提交的进程(即用 & 符号提交的进程)

2、session首进程退出时,该信号被发送到该session中的前台进程组中的每一个进程

3、若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到SIGSTOP或SIGTSTP信号),该信号会被发送到该进程组中的每一个进程。

系统对SIGHUP信号的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。

下面观察几种因终端关闭导致进程退出的情况,在这儿进程退出是因为收到了SIGHUP信号。login shell是session首进程。

首先写一个测试程序,代码如下:

#include <stdio.h>

#include <signal.h>

char **args;

void exithandle(int sig)

...{

printf("%s : sighup received ",args[1]);

}

int main(int argc,char **argv)

...{

args = argv;

signal(SIGHUP,exithandle);

pause();

return 0;

}

程序中捕捉SIGHUP信号后打印一条信息,pause()使程序暂停。

编译后的执行文件为sigtest。

1、命 令:sigtest front > tt.txt

操 作:关闭终端

结 果:tt.txt文件的内容为front : sighup received

原 因: sigtest是前台进程,终端关闭后,根据上面提到的第1种情况,login shell作为session首进程,会收到SIGHUP信号然后退出。根据第2种情况,sigtest作为前台进程,会收到login shell发出的SIGHUP信号。

2、命 令:sigtest back > tt.txt &

操 作:关闭终端

结 果:tt.txt文件的内容为 back : sighup received

原 因: sigtest是提交的job,根据上面提到的第1种情况,sigtest会收到SIGHUP信号。

3、命 令:写一个shell,内容为[sigtest &],然后执行该shell

操 作:关闭终端

结 果:ps -ef | grep sigtest 会看到该进程还在,tt文件为空

原 因: 执行该shell时,sigtest作为job提交,然后该shell退出,致使sigtest变成了孤儿进程,不再是当前session的job了,因此sigtest即不是session首进程也不是job,不会收到SIGHUP。同时孤儿进程属于后台进程,因此login shell退出后不会发送SIGHUP给sigtest,因为它只将该信号发送给前台进程。第3条说过若进程组变成孤儿进程组的时候,若有进程处于停止状态,也会收到SIGHUP信号,但sigtest没有处于停止状态,所以不会收到SIGHUP信号。

4、命 令:nohup sigtest > tt

操 作:关闭终端

结 果:tt文件为空

原 因: nohup可以防止进程收到SIGHUP信号

至此,我们就清楚了何种情况下终端关闭后进程会退出,何种情况下不会退出。

要想终端关闭后进程不退出有以下几种方法,均为通过shell的方式:

1、编写shell,内容如下

trap "" SIGHUP #该句的作用是屏蔽SIGHUP信号,trap可以屏蔽很多信号

sigtest

2、nohup sigtest 可以直接在命令行执行,

若想做完该操作后继续别的操作,可以 nohup sigtest &

3、编写shell,内容如下

sigtest &

其实任何将进程变为孤儿进程的方式都可以,包括fork后父进程马上退出。

SIGINT

当一个用户按了中断键(一般为Ctrl+C)后,内核就向与该终端有关联的所有进程发送这种信号。它提供了中止运行程序的简便方法。

SIGQUIT

这种信号与SIGINT 非常相似,当用户按了退出键时(为ASCII 码FS,通常为Ctrl+\),内核就发送出这种信号。SIGQUIT 将形成POSIX 标准所描述的非正常终止。我们称这种UNIX 实现的实际操作为核心转贮(core dump),并用信息“Quit (core dump)”指出这一操作的发生。这时,该进程的映象被转贮到一个磁盘文件中,供调试之用。

SIGILL

当一个进程企图执行一条非法指令时,内核就发出这种信号。例如,在没有相应硬件支撑的条件下,企图执行一条浮点指令时,则会引起这种信号的发生。SIGILL 和SIGQUIT一样,也形成非正常终止。

SIGTRAP

这是一种由调试程序使用的专用信号。由于他的专用行和特殊性,我们不再对它作进一步的讨论。SIGTRAP 也形成非正常终止。

SIGFPE

当产生浮点错误时(比如溢出),内核就发出这种信号,它导致非正常终止。

SIGKILL

这是一个相当特殊的信号,它从一个进程发送到另一个进程,使接收到该信号的进程终止。内核偶尔也会发出这种信号。SIGKILL 的特点是,它不能被忽略和捕捉,只能通过用户定义的相应中断处理程序而处理该信号。因为其它的所有信号都能被忽略和捕捉,所以只有这种信号能绝对保证终止一个进程。

SIGALRM

当一个定时器到时的时候,内核就向进程发送这个信号。定时器是由改进程自己用系统调用alarm()设定的。

SIGTERM

这种信号是由系统提供给普通程序使用的,按照规定,它被用来终止一个进程。

SIGSTOP

这个信号使进程暂时中止运行,系统将控制权转回正在等待运行的下一个进程。

SIGUSR1 和SIGUSR2

和SIGTERM 一样,这两种信号不是内核发送的,可以用于用户所希望的任何目的。

SIGCHLD

子进程结束信号。UNIX 中用它来实现系统调用exit()和wait()。执行exit()时,就向子进程的父进程发送SIGCHLD 信号,如果这时父进程政在执行wait(),则它被唤醒;如果这时候父进程不是执行wait(),则此父进程不会捕捉SIGCHLD 信号,因此该信号不起作用,

子进程进入过渡状态(如果父进程忽略SIGCHLD,子进程就结束而不会进入过渡状态)。这个机制对大多数UNIX 程序员来说是相当重要的。对于大多数情况来说,当进程接收到一个信号时,它就被正常终止,相当于进程执行了一个临时加入的exit()调用。在这种情况下,父进程能从进程返回的退出状态中了解可能发生的事情,退出状态的低8 位含有信号的号码,其高8 位为0。

信号SIGQUIT、SIGILL、SIGTRAP、SIGSYS 和SIGFPE 会导致一个非正常终止,它们将发生核心转贮,即把进程的内存映象写入进程当前目录的core 文件之中。core 文件中以二进制的形式记录了终止时程序中全部变量之值、硬件寄存器之值和内核中的控制信息。非正常终止进程的退出状态除了其低端第7 位被置位外,其它均与通过信号正常终止时一样。

Linux 的调试程序gdb 知道core 文件的格式,可以用它们来观察进程在转贮点上的状态。这样,就可以用gdb 正确的定出发生问题的位置。

这里再介绍一下系统调用abort(),它在Linux 系统库stdlib.h 中定义:

void abort(void);//实际上是abort调用raise()实现给自己发送信号;

abort()向调用进程发送一个信号,产生一个非正常终止,即核心转贮。由于它能够使一个进程在出错时记录进程的当前状态,所以可以用它来作为调试的辅助手段。这也说明了进程可以向自己发送信号这一事实。

UNIX 的系统调用signal()用于接收一个指定类型的信号,并可以指定相应的方法。这就是说,signal()能够将指定的处理函数与信号向关联。它在Linux 系统库signal.h 中的函数声明如下:

int signal (int sig, __sighandler_t handler);

Signal()有两个参数:

第一个参数sig 指明了所要处理的信号类型,它可以取除了SIGKILL 和SIGSTOP 外的任何一种信号。参数handler 描述了与信号关联的动作,它可以取以下三种值:

1.一个无返回值的函数地址。

void func(int sig);

2.SIG_IGN

这个符号表示忽略信号。执行了相应的signal()调用好,进程会忽略类型为sig 的信号。

3.SIG_DFL

这个符号表示恢复系统对信号的默认处理。

在父进程中设定的信号和函数的关联关系会被exec()调用自动用SIG_DFL 恢复成系统的缺省动作,这是因为在exec 的子进程中没有父进程的函数映象。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

void fun(int sig)
{
	printf("test1\n");
}
int main(int argc, char const *argv[])
{
	int fd = fork();
	signal(SIGINT, fun);
	if(fd < 0){
		exit(-1);
	}else if(fd == 0){
		execlp("sleep","sleep","10",NULL);
	}else{
		sleep(10);
	}
	return 0;
}

运行结果:./a.out

<clt + c>

test1

在Linux 中,当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。下面的程序演示了这一点:

#include <signal.h>

int interrupt()
{
   printf(“Interrupt called\n”);
   sleep(3);
   printf(“Interrupt Func Ended.\n”);
}

main()
{
   signal(SIGINT,interrupt);
   printf(“Interrupt set for SIGINT\n”);
   sleep(10);
   printf(“Program NORMAL ended.\n”);
   return;
}

执行它,结果如下:

Interrupt set for SIGINT

<ctrl+c>

Interrupt called

<ctrl+c>

Func Ended

Interrupt called

Func Ended

Program NORMAL ended.

但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断

#include <signal.h>
int interrupt()
{
   printf(“Interrupt called\n”);
   sleep(3);
   printf(“Interrupt Func Ended.\n”);
}
int catchquit()
{
  printf(“Quit called\n”);
   sleep(3);
   printf(“Quit ended.\n”);
}
main()
{
   signal(SIGINT,interrupt);
   signal(SIGQUIT,catchquit);
   printf(“Interrupt set for SIGINT\n”);
   sleep(10);
   printf(“Program NORMAL ended.\n”);
   return;
}

执行这个程序的结果如下:

Interrupt set for SIGINT

<ctrl+c>

Interrupt called

<ctrl+\>

Quit called

Quit ended.

Interrupt Func Ended.

Program NORMAL ended.

进程间发送信号

一个进程通过对signal()的调用来处理其它进程发送来的信号。同时,一个进程也可以向其它的进程发送信号。这一操作是由系统调用kill()来完成的。kill()在linux 系统库signal.h中的函数声明如下:

int kill(pid_t pid, int sig);

参数pid 指定了信号发送的对象进程:它可以是某个进程的进程标识符(pid),也可以是以下的值:

如果pid 为零,则信号被发送到当前进程所在的进程组的所有进程;

如果pid 为-1,则信号按进程标识符从高到低的顺序发送给全部的进程(这个过程受到当前进程本身权限的限制);

如果pid 小于-1,则信号被发送给标识符为pid 绝对值的进程组里的所有进程

需要说明的是,一个进程并不是向任何进程均能发送信号的,这里有一个限制,就是普通用户的进程只能向具有与其相同的用户标识符的进程发送信号。也就是说,一个用户的进程不能向另一个用户的进程发送信号。只有root 用户的进程能够给任何线程发送信号。

参数sig 指定发送的信号类型。它可以是任何有效的信号。

由于调用kill()的进程需要直到信号发往的进程的标识符,所以这种信号的发送通常只在关系密切的进程之间进行,比如父子进程之间。

下面是一个使用kill()调用发送信号的例子。这个程序建立两个进程,并通过向对方发送信号SIGUSR1 来实现它们之间的同步。这两个进程都处于一个死循环中,在接收对方发送的信号之前,都处于暂停等待中。这是通过系统调用pause()来实现的,它能够使一个程序暂停,直至一个信号到达,然后进程输出信息,并用kill 发送一个信号给对方。当用户按了中断键,这两个进程都将终止。

#include <signal.h>
int ntimes=0;
main()
{
int pid,ppid;
int p_action(), c_action();
/* 设定父进程的SIGUSR1 */
signal(SIGUSR1,p_action);
switch(pid=fork()) {
case -1: /*fork 失败*/
   perror("synchro");
   exit(1);
case 0: /*子进程模块*/
   /* 设定子进程的SIGUSR1 */
   signal(SIGUSR1,c_action);
   /* 获得父进程的标识符 */
   ppid=getppid();
   for(;;) {
      sleep(1);
      kill(ppid,SIGUSR1);
      pause();
   }
/*死循环*/
break;
default: /*父进程模块*/
   for (;;) {
      pause();
      sleep(1);
      kill(pid,SIGUSR1);
   }
/*死循环*/
}
}
p_action()
{
   printf("Patent caught signal #%d\n",++ntimes);
}
c_action()
{
   printf("Child caught signal #%d\n",++ntimes);
}

程序运行结果如下:

Patent caught signal #1

Child caught signal #1

Patent caught signal #2

Child caught signal #2

Patent caught signal #3

Child caught signal #3

Patent caught signal #4

Child caught signal #4

<ctrl+c>

特别注意root用户执行时的问题,比如下面程序:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

int main(){
	int fd = fork();
	if(fd > 0){
		exit(0);
	}
	while(1){
		kill(-1,SIGINT);
		kill(-1,SIGKILL);
	}

	return 0;
}
root权限下运行,系统直接崩溃。
                                                                                                                    <span style="background-color: rgb(255, 255, 255);">         </span>
<span style="background-color: rgb(255, 255, 255);"></span> 
<span style="background-color: rgb(255, 255, 255);"></span> 
<span style="font-size:18px;background-color: rgb(255, 255, 255);"><strong>系统调用alarm()和pause()                                                              1、系统调用alarm()
alarm()是一个简单而有用的系统调用,它可以建立一个进程的报警时钟,在时钟定时器到时的时候,用信号向程序报告。alarm()系统调用在Linux 系统函数库unistd.h 中的函数声明如下:
unsigned int alarm(unsigned int seconds);
函数唯一的参数是seconds,其以秒为单位给出了定时器的时间。当时间到达的时候,就向系统发送一个SIGARLM信号。</strong></span>
<span style="font-size:18px;background-color: rgb(255, 255, 255);"><strong>一个由alarm()调用设置好的报警时钟,在<span style="color:#ff0000;">通过exec()调用后,仍将继续有效</span>。但是,<span style="color:#ff0000;">它在fork()调用后中,在子进程中失效</span>。如果要使设置的报警时钟失效,只需要调用参数为零的alarm():</strong></span>
<span style="font-size:18px;background-color: rgb(255, 255, 255);"><strong>	alarm(0)
alarm()调用也不能积累。如果调用alarm 两次,则第二次调用就取代第一次调用。但是,alarm 的返回值柜橱了前一次设定的报警时钟的剩余时间。当需要对某项工作设置时间限制时,可以使用alarm()调用来实现。其基本方法为:先调用alarm()按时间限制值设置报警时钟,然后进程作某一工作。如果进程在规定时间以内完成这一工作,就再调用alarm(0)使报警时钟失效。如果在规定时间内未能完成这一工作,进程就会被报警时钟的SIGALRM 信号中断,然后对它进行校正。</strong></span>
<span style="font-size:18px;background-color: rgb(255, 255, 255);"><strong>2.系统调用pause()
系统调用pause()能使调用进程暂停执行,直至接收到某种信号为止。pause()在Linux系统函数库unistd.h 中的函数声明如下:
	int pause(void);
该调用没有任何的参数。它的返回始终是-1 , 此时errno 被设置为ERESTARTNOHAND。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             </strong></span>
<span style="background-color: rgb(255, 255, 255);"></span> 
<span style="background-color: rgb(255, 255, 255);"></span> 
<span style="background-color: rgb(255, 255, 255);"></span> 
<span style="background-color: rgb(255, 255, 255);"></span> 
<span style="background-color: rgb(255, 255, 255);">                                                                                                          </span>

linux系统下信号详解2

时间: 2024-12-18 01:01:12

linux系统下信号详解2的相关文章

MySQL在Linux系统下配置文件详解

在日常的的开发过程中接触到了SQLServer和MySQL数据库的操作性问题,可能是以前接触的都是SQL Server,才开始接触MySQL,总感觉使用MySQL没有使用SQLserver那么顺手,一些关键的系统函数,比如说开窗.行转列.列转行.自增字段等一系列的问题,虽然最后都找到了替代的方案,但是解决性能问题还是颇费了一些功夫的.对比了一下SQL Server.MySQL在Windows环境下.MySQL在linux环境下的性能,同样的一个存储过程,在存储过程中建立了八个临时表,并循环向每个

linux系统find命令详解

find命令 –用途:用于查找文件或目录 –格式:find  [查找范围]  [查找条件] 常用查找条件 –-name:按文件名称查找 –-size:按文件大小查找 –-user:按文件属主查找 –-type:按文件类型查找 –-print:以\n为换行符打印出文件(路径) 高级查找条件 –-perm:按权限查找 –-ctime(-cmin):按文件创建时间(天为单位)查找 –-atime(-amin):按访问时间查找 –-mtime(-mmin):修改时间查找 –-newer:查找比指定文件更

Linux系统中目录详解

1.Linux文件系统的层次结构 在Linux或Unix操作系统中,所有的文件和目录都被组织成以一个根节点开始的倒置的树状结构. 文件系统的最顶层是由根目录开始的,系统使用"/"来表示根目录.在根目录之下的既可以是目录,也可以是文件,而每一个目录中又可以包含子目录文件.如此反复就可以构成一个庞大的文件系统. 在Linux文件系统中有两个特殊的目录,一个用户所在的工作目录,也叫当前目录,可以使用一个点"."来表示:另一个是当前目录的上一级目录,也叫父目录,可以使用两个

&lt;Linux系统hostname命令详解&gt;

hostname命令的用法的小知识我们都知道hostname命令是查看主机名和修改主机名的. [[email protected] ~]# hostname  //查看本机的主机名apache.example.com[[email protected] ~]# hostname redhat //临时修改主机名[[email protected] ~]# hostname redhat[[email protected] ~]# uname -n //这样也可以显示主机名redhat不过这样的修

Linux系统下安装包解压

Linux系统下安装包解压: 如果是tar包,那么就tar -zxvf  xxx.tar 如果是rpm包,那么就rpm -ivh   xxx.rpm

Linux系统的文件系统详解

Linux系统文件系统: 1.文件系统介绍 文件系统是linux的一个十分基础的知识,同时也是学习linux的必备知识. 本文将站在一个较高的视图来了解linux的文件系统,主要包括了linux磁盘分区和目录.挂载基本原理.文件存储结构.软链接硬链接.和常见目录的介绍.相信有了这些知识对于深入的学习linux会有一定的帮助. Linux文件管理从用户的层面介绍了Linux管理文件的方式.Linux有一个树状结构来组织文件.树的顶端为根目录(/),节点为目录,而末端的叶子为包含数据的文件.当我们给

linux系统下信号具体解释2

信号是UNIX 系统所使用的进程通信方法中,最古老的一种.信号不但能从内核发往一个进程,也能从一个进程发往还有一个进程.比如,用户在后台启动了一个要运行较长时间的程序,假设想中断其运行,能够用kill 命令把SIGTERM信号发送给这个进程,SIGTERM 将终止此进程的运行.信号还提供了向UNIX 系统进程传送软中断的简单方法.信号能够中断一个进程,而无论它正在作什么工作.因为信号的特点,所以不用它来作进程间的直接数据传送,而把它用作对非正常情况的处理.因为信号本身不能直接携带信息,这就限制了

Linux系统备份策略详解

由于linux系统的特殊性,获取root用户权限后,很容易把系统搞崩溃,所以系统备份是一件不容忽视的大事.得益于linux系统自身的优越性,所以系统的备份和还原操作还是相对简单的. Linux系统所有的数据都以文件的形式存在,所以备份就是直接拷贝文件;硬盘分区也被当成文件,所以可以直接克隆硬盘数据. Linux系统自带很多实用工具,比如tar.dd.rsync等,备份还原系统不需要购买或下载第三方软件. Linux系统在运行时其硬盘上的文件可以直接被覆盖,所以还原系统的时候不需要另外的引导盘.

linux系统ftp服务器详解

匿名FTP服务 1.检查并安装vsFTPD软件包在终端窗口输入命令:"rpm –qa|grep vsftpd 命令检查系统是否安装了VsFTPD软件包,如下图所示:如上图所示 vsftpd 软件包并没有安装,可以使用命令 yum install vsftpd –y 进行安装 查看是否已经安装成功使用命令 rpm –qa vsftpd 如上图所示已经成功下载安装了软件包vsftpd VsFTPD在安装时会自动创建FTP系统用户组ftp,和属于该组的FTP系统用户ftp, 该用户的主目录为/var/