精华内容
下载资源
问答
  • 什么是信号? 信号是 Linux 进程间通信最古老方式。...“中断”在我们生活中经常遇到,譬如,我正在房间打游戏,突然送快递来了,把正在玩游戏我给“中断”了,我去签收快递( 处理中断 ),处理完成后,再继

    什么是信号?

    信号是 Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。


    “中断”在我们生活中经常遇到,譬如,我正在房间里打游戏,突然送快递的来了,把正在玩游戏的我给“中断”了,我去签收快递( 处理中断 ),处理完成后,再继续玩我的游戏。这里我们学习的“信号”就是属于这么一种“中断”。我们在终端上敲“Ctrl+c”,就产生一个“中断”,相当于产生一个信号,接着就会处理这么一个“中断任务”(默认的处理方式为中断当前进程)。


    信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件。


    一个完整的信号周期包括三个部分:信号的产生,信号在进程中的注册,信号在进程中的注销,执行信号处理函数。如下图所示:


    注意:这里信号的产生,注册,注销时信号的内部机制,而不是信号的函数实现。


    Linux 可使用命令:kill -l("l" 为字母),查看相应的信号。



    列表中,编号为 1 ~ 31 的信号为传统 UNIX 支持的信号,是不可靠信号(非实时的),编号为 32 ~ 63 的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。非可靠信号一般都有确定的用途及含义,  可靠信号则可以让用户自定义使用。更多详情,请看《Linux信号列表》


    信号的产生方式

    1)当用户按某些终端键时,将产生信号。

    终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT,终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT,终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。


    2)硬件异常将产生信号。

    除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。


    3)软件异常将产生信号。

    当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。


    4)调用 kill() 函数将发送信号。

    注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。


    5)运行 kill 命令将发送信号。

    此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程。


    信号的常用操作

    发送信号

    所需头文件:

    #include <sys/types.h>

    #include <signal.h>


    int kill(pid_t pid, int signum);

    功能:

    给指定进程发送信号。

    注意:使用 kill() 函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。

    参数:

    pid: 取值有 4 种情况:

    pid > 0: 将信号传送给进程 ID 为pid的进程。

    pid = 0: 将信号传送给当前进程所在进程组中的所有进程。

    pid = -1: 将信号传送给系统内所有的进程。

    pid < -1: 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。

    signum: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母)进行相应查看。

    返回值:

    成功:0

    失败:-1


    下面为测试代码,本来父子进程各自每隔一秒打印一句话,3 秒后,父进程通过 kill() 函数给子进程发送一个中断信号 SIGINT( 2 号信号),最终,子进程结束,剩下父进程在打印信息:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <signal.h>
    
    int main(int argc, char *argv[])
    {
    	pid_t pid;
    	int i = 0;
    
    	pid = fork(); // 创建进程
    	if( pid < 0 ){ // 出错
    		perror("fork");
    	}
    	
    	if(pid == 0){ // 子进程
    		while(1){
    			printf("I am son\n");
    			sleep(1);
    		}
    	}else if(pid > 0){ // 父进程
    		while(1){
    			printf("I am father\n");
    			sleep(1);
    			
    			i++;
    			if(3 == i){// 3秒后
    				kill(pid, SIGINT); // 给子进程 pid ,发送中断信号 SIGINT
    				// kill(pid, 2); // 等级于kill(pid, SIGINT);
    			}
    		}
    	}
    
    	return 0;
    }



    运行结果如下:



    等待信号

    所需头文件:

    #include <unistd.h>


    int pause(void);

    功能:

    等待信号的到来(此函数会阻塞)。将调用进程挂起直至捕捉到信号为止,此函数通常用于判断信号是否已到。

    参数:

    无。

    返回值:

    直到捕获到信号才返回 -1,且 errno 被设置成 EINTR。


    测试代码如下:

    #include <unistd.h>
    #include <stdio.h>
    
    int main(int argc, char *argv[])
    {
    	printf("in pause function\n");
    	pause();
    	
    	return 0;
    }



    没有产生信号前,进程一直阻塞在 pause() 不会往下执行,假如,我们按“Ctrl+c”,pause() 会捕获到此信号,中断当前进程。


    处理信号

    一个进程收到一个信号的时候,可以用如下方法进行处理:

    1)执行系统默认动作

    对大多数信号来说,系统默认动作是用来终止该进程。


    2)忽略此信号

    接收到此信号后没有任何动作。


    3)执行自定义信号处理函数

    用用户定义的信号处理函数处理该信号。


    注意:SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。


    产生一个信号,我们可以让其执行自定义信号处理函数。假如有函数 A, B, C,我们如何确定信号产生后只调用函数 A,而不是函数 B 或 C。这时候,我们需要一种规则规定,信号产生后就调用函数 A,就像交通规则一样,红灯走绿灯行,信号注册函数 signal() 就是做这样的事情


    所需头文件:

    #include <signal.h>


    typedef void (*sighandler_t)(int);// 回调函数的声明
    sighandler_t signal(int signum,sighandler_t handler);

    功能:

    注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞

    参数:

    signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母)进行相应查看。

    handler: 取值有 3 种情况:

    SIG_IGN:忽略该信号
    SIG_DFL:执行系统默认动作
    信号处理函数名:自定义信号处理函数,如:fun

    回调函数的定义如下:

    void fun(int signo)

    {

    // signo 为触发的信号,为 signal() 第一个参数的值

    }

    注意信号处理函数应该为可重入函数,关于可重入函数的更多详情,请《浅谈可重入函数与不可重入函数》

    返回值

    成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,必须在前面先声明此函数指针的类型

    失败:返回 SIG_ERR


    示例一:

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    
    // 信号处理函数
    void signal_handler(int signo)
    {
    	if(signo == SIGINT){
    		printf("recv SIGINT\n");
    	}else if(signo == SIGQUIT){
    		printf("recv SIGQUIT\n");
    	}
    }
    
    int main(int argc, char *argv[])
    {
    	printf("wait for SIGINT OR SIGQUIT\n");
    	
    	/* SIGINT: Ctrl+c ; SIGQUIT: Ctrl+\ */
    	// 信号注册函数
    	signal(SIGINT, signal_handler);
    	signal(SIGQUIT, signal_handler);
    	
    	// 等待信号
    	pause();
    	pause();
    	
    	return 0;
    }



    在终端里敲“Ctrl+c”或“Ctrl+\”,自动调用其指定好的回调函数 signal_handler():



    示例二:

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    
    // 回调函数的声明
    typedef void (*sighandler_t)(int);
    
    void fun1(int signo)
    {
    	printf("in fun1\n");
    }
    
    void fun2(int signo)
    {
    	printf("in fun2\n");
    }
    
    int main(int argc, char *argv[])
    {
    	sighandler_t previous = NULL;
    	
    	// 第一次返回 NULL
    	previous = signal(SIGINT,fun1); 
    	if(previous == NULL)
    	{
    		printf("return fun addr is NULL\n");
    	}
    	
    	// 下一次返回此信号上一次注册的信号处理函数的地址。
    	previous = signal(SIGINT, fun2);
    	if(previous == fun1)
    	{
    		printf("return fun addr is fun1\n");
    	}
    	
    	// 还是返回 NULL,因为处理的信号变了
    	previous = signal(SIGQUIT,fun1);
    	if(previous == NULL)
    	{
    		printf("return fun addr is NULL\n");
    	}
    	
    	return 0;
    }



    运行结果如下:



    信号集与信号阻塞集

    信号集

    为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux 系统中引入了信号集(信号的集合)。这个信号集有点类似于我们的 QQ 群,一个个的信号相当于 QQ 群里的一个个好友


    信号集是用来表示多个信号的数据类型(sigset_t),其定义路径为:/usr/include/i386-linux-gnu/bits/sigset.h。


    信号集相关的操作主要有如下几个函数

    #include <signal.h>
    int sigemptyset(sigset_t *set);
    int sigfillset(sigset_t *set);
    int sigismember(const sigset_t *set, int signum);
    int sigaddset(sigset_t *set, int signum);
    int sigdelset(sigset_t *set, int signum);



    以上几个函数的用法都是比较简单,这里就不一一介绍,我们通过一个例子来学习其用法:

    #include <signal.h>
    #include <stdio.h>
    
    int main(int argc, char *argv[])
    {
    	sigset_t set;	// 定义一个信号集变量
    	int ret = 0;
    
    	sigemptyset(&set); // 清空信号集的内容
    	
    	// 判断 SIGINT 是否在信号集 set 里
    	// 在返回 1, 不在返回 0
    	ret = sigismember(&set, SIGINT);
    	if(ret == 0){
    		printf("SIGINT is not a member of set \nret = %d\n", ret);
    	}
    		
    	sigaddset(&set, SIGINT); // 把 SIGINT 添加到信号集 set
    	sigaddset(&set, SIGQUIT);// 把 SIGQUIT 添加到信号集 set
    	
    	// 判断 SIGINT 是否在信号集 set 里
    	// 在返回 1, 不在返回 0
    	ret = sigismember(&set, SIGINT);
    	if(ret == 1){
    		printf("SIGINT is a member of set \nret = %d\n", ret);
    	}
    	
    	sigdelset(&set, SIGQUIT); // 把 SIGQUIT 从信号集 set 移除
    	
    	// 判断 SIGQUIT 是否在信号集 set 里
    	// 在返回 1, 不在返回 0
    	ret = sigismember(&set, SIGQUIT);
    	if(ret == 0){
    		printf("SIGQUIT is not a member of set \nret = %d\n", ret);
    	}
    	
    	return 0;
    }



    运行结果如下:



    信号阻塞集(屏蔽集、掩码)

    信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。


    所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。


    我们可以通过 sigprocmask() 修改当前的信号掩码来改变信号的阻塞情况。


    所需头文件:

    #include <signal.h>


    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

    功能:

    检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。

    参数:

    how: 信号阻塞集合的修改方法,有 3 种情况:

    SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。
    SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。
    SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。

    set: 要操作的信号集地址。

    若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。

    oldset: 保存原先信号阻塞集地址

    返回值:

    成功:0,

    失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。


    注意:不能阻塞 SIGKILL 和 SIGSTOP 等信号,但是当 set 参数包含这些信号时 sigprocmask() 不返回错误,只是忽略它们。另外,阻塞 SIGFPE 这样的信号可能导致不可挽回的结果,因为这些信号是由程序错误产生的,忽略它们只能导致程序无法执行而被终止。


    示例代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    
    int main(int argc, char *argv[])
    {
    	sigset_t set; // 信号集合
    	int i = 0;
    	
    	sigemptyset(&set); // 清空信号集合
    	sigaddset(&set, SIGINT); // SIGINT 加入 set 集合
    	
    	while(1)
    	{
    		// set 集合加入阻塞集,在没有移除前,SIGINT 会被阻塞
    		sigprocmask(SIG_BLOCK, &set, NULL);
    		for(i=0; i<5; i++)
    		{
    			printf("SIGINT signal is blocked\n");
    			sleep(1);
    		}
    		
    		// set 集合从阻塞集中移除
    		// 假如 SIGINT 信号在被阻塞时发生了
    		// 此刻,SIGINT 信号立马生效,中断当前进程
    		sigprocmask(SIG_UNBLOCK, &set, NULL);
    		for(i=0; i<5; i++)
    		{
    			printf("SIGINT signal unblocked\n");
    			sleep(1);
    		}
    	}
    	
    	return 0;
    }



    可靠信号的操作

    从 UNIX 系统继承过来的信号(SIGHUP~SIGSYS,前 32 个)都是不可靠信号,不支持排队(多次发送相同的信号, 进程可能只能收到一次,可能会丢失)。


    SIGRTMIN 至 SIGRTMAX 的信号支持排队(发多少次, 就可以收到多少次, 不会丢失),故称为可靠信号。


    可靠信号就是实时信号,非可靠信号就是非实时信号。


    signal() 函数只能提供简单的信号安装操作,使用 signal() 函数处理信号比较简单,只要把要处理的信号和处理函数列出即可。


    signal() 函数主要用于前面 32 种不可靠、非实时信号的处理,并且不支持信号传递信息。


    Linux 提供了功能更强大的 sigaction() 函数,此函数可以用来检查和更改信号处理操作,可以支持可靠、实时信号的处理,并且支持信号传递信息。


    下面我们一起学习其相关函数的使用。


    所需头文件:

    #include <signal.h>


    int sigqueue(pid_t pid, int sig, const union sigval value);

    功能:

    给指定进程发送信号。

    参数:

    pid: 进程号。

    sig: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母)进行相应查看。

    value: 通过信号传递的参数。

    union sigval 类型如下:


    union sigval
    {
    	int   sival_int;
    	void *sival_ptr;
    };


    返回值:

    成功:0

    失败:-1


    int sigaction(int signum,const struct sigaction *act, struct sigaction *oldact );

    功能:

    检查或修改指定信号的设置(或同时执行这两种操作)。

    参数:

    signum:要操作的信号。

    act:   要设置的对信号的新处理方式(设置)。

    oldact:原来对信号的处理方式(设置)。


    如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式(设置)存入 oldact。


    返回值:

    成功:0

    失败:-1


    信号设置结构体:

    struct sigaction
    {
    	/*旧的信号处理函数指针*/
    	void (*sa_handler)(int signum) ;
    	
    	/*新的信号处理函数指针*/
    	void (*sa_sigaction)(int signum, siginfo_t *info, void *context);
    	
    	sigset_t sa_mask;/*信号阻塞集*/
    	
    	int sa_flags;/*信号处理的方式*/
    };


    sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给 sa_sigaction、sa_handler 两者之一赋值,其取值如下:

    SIG_IGN:忽略该信号

    SIG_DFL:执行系统默认动作

    处理函数名:自定义信号处理函数

    sa_mask:信号阻塞集

    sa_flags:用于指定信号处理的行为,它可以是一下值的“按位或”组合:

    SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)

    SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。

    SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。

    SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

    SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

    SA_SIGINFO使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数


    信号处理函数:
    void (*sa_sigaction)( int signum, 
    siginfo_t *info, void *context );
    参数说明:

    signum:信号的编号。
    info:记录信号发送进程信息的结构体,进程信息结构体路径:/usr/include/i386-linux-gnu/bits/siginfo.h,其结构体详情请点此链接
    context:可以赋给指向 ucontext_t 类型的一个对象的指针,以引用在传递信号时被中断的接收进程或线程的上下文,其结构体详情点此链接


    下面我们做这么一个例子,一个进程在发送信号,一个进程在接收信号的发送。

    发送信号示例代码如下:

    #include <stdio.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    /*******************************************************
    *功能:     发 SIGINT 信号及信号携带的值给指定的进程
    *参数:		argv[1]:进程号
    			argv[2]:待发送的值(默认为100)
    *返回值:	0
    ********************************************************/
    int main(int argc, char *argv[])
    {
    	if(argc >= 2)
    	{
    		pid_t pid,pid_self;
    		union sigval tmp;
    
    		pid = atoi(argv[1]); // 进程号
    		if( argc >= 3 )
    		{
    			tmp.sival_int = atoi(argv[2]);
    		}
    		else
    		{
    			tmp.sival_int = 100;
    		}
    		
    		// 给进程 pid,发送 SIGINT 信号,并把 tmp 传递过去
    		sigqueue(pid, SIGINT, tmp);
    		
    		pid_self = getpid(); // 进程号
    		printf("pid = %d, pid_self = %d\n", pid, pid_self);
    		
    	}
    	
    	return 0;
    }



    接收信号示例代码如下:

    #include <signal.h>
    #include <stdio.h>
    
    // 信号处理回电函数
    void signal_handler(int signum, siginfo_t *info, void *ptr)
    {
    	printf("signum = %d\n", signum); // 信号编号
    	printf("info->si_pid = %d\n", info->si_pid); // 对方的进程号
    	printf("info->si_sigval = %d\n", info->si_value.sival_int); // 对方传递过来的信息
    }
    
    int main(int argc, char *argv[])
    {
    	struct sigaction act, oact;
    	
    	act.sa_sigaction = signal_handler; //指定信号处理回调函数
    	sigemptyset(&act.sa_mask); // 阻塞集为空
    	act.sa_flags = SA_SIGINFO; // 指定调用 signal_handler
    	
    	// 注册信号 SIGINT
    	sigaction(SIGINT, &act, &oact);
    	
    	while(1)
    	{
    		printf("pid is %d\n", getpid()); // 进程号
    		
    		pause(); // 捕获信号,此函数会阻塞
    	}
    	
    	return 0;
    }


    两个终端分别编译代码,一个进程接收,一个进程发送,运行结果如下:

    展开全文
  • 僵尸进程不断生成,导致服务器hang住,真实案例在眼前发生,对于线上生产环境问题,秉着应用恢复正常为第一要义,凭借高超重启技术,先重启服务器然后重启应用恢复,感叹重启大法好,一切又回归了平静,在充实而...

    目录

    一、什么是进程?

    二、进程的状态

    三、不可中断进程和僵尸进程

    四、iowait升高分析

    五、僵尸进程分析

     


    僵尸进程不断生成,导致服务器hang住,真实的案例在眼前发生,对于线上生产环境问题,秉着应用恢复正常为第一要义,凭借高超的重启技术,先重启服务器然后重启应用恢复,感叹重启大法好,一切又回归了平静,在充实而忙碌的日子里等待下一次的异常后的重启操作。。。一次又一次,重启操作越来越熟练,僵尸进程还是那些进程。终于有一天,重启熟练度达成新成就99%,没有了上升空间...这天,僵尸进程又来了....

    一、什么是进程?

         进程是指在系统中正在运行的一个应用程序,是资源拥有的基本单位,且每个进程拥有独立的地址空间。一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。线程是CPU独立运行和独立调度的基本单位。

    二、进程的状态

    top 和 ps 是最常用的查看进程状态的工具,从 top 的输出示例,S 列(也就是 Status 列)表示进程的状态。如下出现R、D、Z、S、I 等几个状态:

    
    [root@localhost ~]# top
      PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    28961 root      20   0   43816   3148   4040 R   3.2  0.0   0:00.01 top
      620 root      20   0   37280  33676    908 D   0.3  0.4   0:00.01 app
        1 root      20   0  160072   9416   6752 S   0.0  0.1   0:37.64 systemd
     1896 root      20   0       0      0      0 Z   0.0  0.0   0:00.00 devapp
        2 root      20   0       0      0      0 S   0.0  0.0   0:00.10 kthreadd
        4 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 kworker/0:0H
        6 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 mm_percpu_wq
        7 root      20   0       0      0      0 S   0.0  0.0   0:06.37 ksoftirqd/0
    • R 是 Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行。
    • D 是 Disk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断。
    • Z 是 Zombie 的缩写,表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)。
    • S 是 Interruptible Sleep 的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。
    • I 是 Idle 的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。一般硬件交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升高, I 状态的进程却不会。

    除了以上 5 个状态,进程还包括下面这 2 个状态。

    第一个是 T 或者 t,也就是 Stopped 或 Traced 的缩写,表示进程处于暂停或者跟踪状态。向一个进程发送 SIGSTOP 信号,它就会因响应这个信号变成暂停状态(Stopped);再向它发送 SIGCONT 信号,进程又会恢复运行(如果进程是终端里直接启动的,则需要你用 fg 命令,恢复到前台运行)。而当你用调试器(如 gdb)调试一个进程时,在使用断点中断进程后,进程就会变成跟踪状态,这其实也是一种特殊的暂停状态,只不过你可以用调试器来跟踪并按需要控制进程的运行。

    另一个是 X,也就是 Dead 的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令中看到它。

    三、不可中断进程和僵尸进程

    1、不可中断状态(D状态)

    进程不可中断状态其实是为了保证进程数据与硬件状态一致,正常情况下,不可中断状态在很短时间内就会结束。所以,短时的不可中断状态进程,我们一般可以忽略。但如果系统或硬件发生了故障,进程可能会在不可中断状态保持很久,甚至导致系统中出现大量不可中断进程。这时,就很有可能引起I/O 等性能问题。

    2、僵尸进程(Z状态)

    僵尸进程,在多进程应用经常会碰到。

    正常情况下,当一个进程创建了子进程后,它应该通过系统调用 wait() 或者 waitpid() 等待子进程结束,回收子进程的资源;而且子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册 SIGCHLD 信号的处理函数,异步回收资源。

    如果父进程没有通过系统调用 wait() 或者 waitpid() 等待子进程结束,或是子进程执行太快,父进程还没来得及处理子进程状态,子进程就已经提前退出,那这时的子进程就会变成僵尸进程。

    通常,僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由 init 进程回收后也会消亡。一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。

    系统所能使用的进程号是有限的,大量的僵尸进程会用尽 PID 进程号,导致新进程不能创建。该为僵尸进程的危害,应当避免 。

    两个概念:进程组和会话。它们用来管理一组相互关联的进程。
    进程组表示一组相互关联的进程,比如每个子进程都是父进程所在组的成员;
    会话是指共享同一个控制终端的一个或多个进程组。

    四、iowait升高分析

    磁盘 I/O 导致了 iowait 升高,不过, iowait 高不一定代表 I/O 有性能瓶颈。当系统中只有 I/O 类型的进程在运行时,iowait 也会很高,但实际上,磁盘的读写远没有达到性能瓶颈的程度。

    一般步骤如下:

    ①用dstat ,pidstat命令同时查看cpu和i/o对比情况(如 dstat 1 10 间隔1秒输出10组数据),通过结果可以发现iowait升高时,磁盘读请求(read)升高,推断iowait升高是磁盘读导致

    ②定位磁盘读的进程,等待 I/O 的进程一般是不可中断状态,使用top命令查看处于不可中断状态(D)的进程PID 或 用 ps 命令找到的 D 状态(即不可中断状态)的进程,多为可疑进程。

    ③查看对应进程的磁盘读写情况,使用pidstat命令,加上-d参数,可以看到i/o使用情况(如 pidstat -d -p <pid> 1 3),发现处于不可中断状态的进程都没有进行磁盘读写

    ④继续使用pidstat命令,查看所有进程的i/o情况(pidstat -d 1 20),可以定位到进行磁盘读写的进程。我们知道进程访问磁盘,需要使用系统调用

    ⑤使用strace查看进程的系统调用 strace -p <pid>,发现报了 strace:attach :ptrace(PTRACE_SIZE,6028):Operation not peritted,说没有权限,使用的root权限,所以这个时候就要查看进程的状态是否正

    ⑥ps aux | grep <pid> 发现进程处于Z状态,已经变成了僵尸进程,所以不能进行系统调用分析了

    ⑦既然top和pidstat都不能找出问题,使用基于事件记录的动态追踪工具

    ⑧在终端中运行 perf record,持续一会儿(例如 15 秒),然后按 Ctrl+C 退出,再运行 perf report 查看报告,观察调用栈信息,检查是否有磁盘读操作

    ⑨从代码层面分析,究竟是哪里出现了直接读请求。

    五、僵尸进程分析

    僵尸进程是因为父进程没有回收子进程的资源而出现的,那么,要解决掉它们,找出父进程,然后在父进程里解决。

    ①使用 pstree 找出父进程后

    ②查看父进程的代码,接着查看 app 应用程序的代码,看看子进程结束的处理是否正确,比如有没有调用 wait() 或 waitpid() ,抑或是,有没有注册 SIGCHLD 信号的处理函数。

     

     

    展开全文
  • 什么是信号? 信号是 Linux 进程间通信最古老...“中断”在我们生活中经常遇到,譬如,我正在房间打游戏,突然送快递来了,把正在玩游戏我给“中断”了,我去签收快递( 处理中断 ),处理完成后,再继

    什么是信号?

    信号是 Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。


    “中断”在我们生活中经常遇到,譬如,我正在房间里打游戏,突然送快递的来了,把正在玩游戏的我给“中断”了,我去签收快递( 处理中断 ),处理完成后,再继续玩我的游戏。这里我们学习的“信号”就是属于这么一种“中断”。我们在终端上敲“Ctrl+c”,就产生一个“中断”,相当于产生一个信号,接着就会处理这么一个“中断任务”(默认的处理方式为中断当前进程)。


    信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件。


    一个完整的信号周期包括三个部分:信号的产生,信号在进程中的注册,信号在进程中的注销,执行信号处理函数。如下图所示:


    注意:这里信号的产生,注册,注销时信号的内部机制,而不是信号的函数实现。


    linux 可使用命令:kill -l("l" 为字母),查看相应的信号。



    列表中,编号为 1 ~ 31 的信号为传统 UNIX 支持的信号,是不可靠信号(非实时的),编号为 32 ~ 63 的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。非可靠信号一般都有确定的用途及含义,  可靠信号则可以让用户自定义使用。更多详情,请看《Linux信号列表》


    信号的产生方式

    1)当用户按某些终端键时,将产生信号。

    终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT,终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT,终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。


    2)硬件异常将产生信号。

    除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。


    3)软件异常将产生信号。

    当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。


    4)调用 kill() 函数将发送信号。

    注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。


    5)运行 kill 命令将发送信号。

    此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程。


    信号的常用操作

    发送信号

    所需头文件:

    #include <sys/types.h>

    #include <signal.h>


    int kill(pid_t pid, int signum);

    功能:

    给指定进程发送信号。

    注意:使用 kill() 函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。

    参数:

    pid: 取值有 4 种情况:

    pid > 0: 将信号传送给进程 ID 为pid的进程。

    pid = 0: 将信号传送给当前进程所在进程组中的所有进程。

    pid = -1: 将信号传送给系统内所有的进程。

    pid < -1: 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。

    signum: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母)进行相应查看。

    返回值:

    成功:0

    失败:-1


    下面为测试代码,本来父子进程各自每隔一秒打印一句话,3 秒后,父进程通过 kill() 函数给子进程发送一个中断信号 SIGINT( 2 号信号),最终,子进程结束,剩下父进程在打印信息:

    [cpp] view plain copy
    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <unistd.h>  
    4. #include <sys/types.h>  
    5. #include <signal.h>  
    6.   
    7. int main(int argc, char *argv[])  
    8. {  
    9.     pid_t pid;  
    10.     int i = 0;  
    11.   
    12.     pid = fork(); // 创建进程  
    13.     if( pid < 0 ){ // 出错  
    14.         perror("fork");  
    15.     }  
    16.       
    17.     if(pid == 0){ // 子进程  
    18.         while(1){  
    19.             printf("I am son\n");  
    20.             sleep(1);  
    21.         }  
    22.     }else if(pid > 0){ // 父进程  
    23.         while(1){  
    24.             printf("I am father\n");  
    25.             sleep(1);  
    26.               
    27.             i++;  
    28.             if(3 == i){// 3秒后  
    29.                 kill(pid, SIGINT); // 给子进程 pid ,发送中断信号 SIGINT  
    30.                 // kill(pid, 2); // 等级于kill(pid, SIGINT);  
    31.             }  
    32.         }  
    33.     }  
    34.   
    35.     return 0;  
    36. }  


    运行结果如下:



    等待信号

    所需头文件:

    #include <unistd.h>


    int pause(void);

    功能:

    等待信号的到来(此函数会阻塞)。将调用进程挂起直至捕捉到信号为止,此函数通常用于判断信号是否已到。

    参数:

    无。

    返回值:

    直到捕获到信号才返回 -1,且 errno 被设置成 EINTR。


    测试代码如下:

    [cpp] view plain copy
    1. #include <unistd.h>  
    2. #include <stdio.h>  
    3.   
    4. int main(int argc, char *argv[])  
    5. {  
    6.     printf("in pause function\n");  
    7.     pause();  
    8.       
    9.     return 0;  
    10. }  


    没有产生信号前,进程一直阻塞在 pause() 不会往下执行,假如,我们按“Ctrl+c”,pause() 会捕获到此信号,中断当前进程。


    处理信号

    一个进程收到一个信号的时候,可以用如下方法进行处理:

    1)执行系统默认动作

    对大多数信号来说,系统默认动作是用来终止该进程。


    2)忽略此信号

    接收到此信号后没有任何动作。


    3)执行自定义信号处理函数

    用用户定义的信号处理函数处理该信号。


    注意:SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。


    产生一个信号,我们可以让其执行自定义信号处理函数。假如有函数 A, B, C,我们如何确定信号产生后只调用函数 A,而不是函数 B 或 C。这时候,我们需要一种规则规定,信号产生后就调用函数 A,就像交通规则一样,红灯走绿灯行,信号注册函数 signal() 就是做这样的事情


    所需头文件:

    #include <signal.h>


    typedef void (*sighandler_t)(int);// 回调函数的声明
    sighandler_t signal(int signum,sighandler_t handler);

    功能:

    注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞

    参数:

    signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母)进行相应查看。

    handler: 取值有 3 种情况:

    SIG_IGN:忽略该信号
    SIG_DFL:执行系统默认动作
    信号处理函数名:自定义信号处理函数,如:fun

    回调函数的定义如下:

    void fun(int signo)

    {

    // signo 为触发的信号,为 signal() 第一个参数的值

    }

    注意信号处理函数应该为可重入函数,关于可重入函数的更多详情,请《浅谈可重入函数与不可重入函数》

    返回值

    成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,必须在前面先声明此函数指针的类型

    失败:返回 SIG_ERR


    示例一:

    [cpp] view plain copy
    1. #include <stdio.h>  
    2. #include <signal.h>  
    3. #include <unistd.h>  
    4.   
    5. // 信号处理函数  
    6. void signal_handler(int signo)  
    7. {  
    8.     if(signo == SIGINT){  
    9.         printf("recv SIGINT\n");  
    10.     }else if(signo == SIGQUIT){  
    11.         printf("recv SIGQUIT\n");  
    12.     }  
    13. }  
    14.   
    15. int main(int argc, char *argv[])  
    16. {  
    17.     printf("wait for SIGINT OR SIGQUIT\n");  
    18.       
    19.     /* SIGINT: Ctrl+c ; SIGQUIT: Ctrl+\ */  
    20.     // 信号注册函数  
    21.     signal(SIGINT, signal_handler);  
    22.     signal(SIGQUIT, signal_handler);  
    23.       
    24.     // 等待信号  
    25.     pause();  
    26.     pause();  
    27.       
    28.     return 0;  
    29. }  

    在终端里敲“Ctrl+c”或“Ctrl+\”,自动调用其指定好的回调函数 signal_handler():



    示例二:

    [cpp] view plain copy
    1. #include <stdio.h>  
    2. #include <signal.h>  
    3. #include <unistd.h>  
    4.   
    5. // 回调函数的声明  
    6. typedef void (*sighandler_t)(int);  
    7.   
    8. void fun1(int signo)  
    9. {  
    10.     printf("in fun1\n");  
    11. }  
    12.   
    13. void fun2(int signo)  
    14. {  
    15.     printf("in fun2\n");  
    16. }  
    17.   
    18. int main(int argc, char *argv[])  
    19. {  
    20.     sighandler_t previous = NULL;  
    21.       
    22.     // 第一次返回 NULL  
    23.     previous = signal(SIGINT,fun1);   
    24.     if(previous == NULL)  
    25.     {  
    26.         printf("return fun addr is NULL\n");  
    27.     }  
    28.       
    29.     // 下一次返回此信号上一次注册的信号处理函数的地址。  
    30.     previous = signal(SIGINT, fun2);  
    31.     if(previous == fun1)  
    32.     {  
    33.         printf("return fun addr is fun1\n");  
    34.     }  
    35.       
    36.     // 还是返回 NULL,因为处理的信号变了  
    37.     previous = signal(SIGQUIT,fun1);  
    38.     if(previous == NULL)  
    39.     {  
    40.         printf("return fun addr is NULL\n");  
    41.     }  
    42.       
    43.     return 0;  
    44. }  

    运行结果如下:



    信号集与信号阻塞集

    信号集

    为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux 系统中引入了信号集(信号的集合)。这个信号集有点类似于我们的 QQ 群,一个个的信号相当于 QQ 群里的一个个好友


    信号集是用来表示多个信号的数据类型(sigset_t),其定义路径为:/usr/include/i386-linux-gnu/bits/sigset.h。


    信号集相关的操作主要有如下几个函数:

    [cpp] view plain copy
    1. #include <signal.h>  
    2. int sigemptyset(sigset_t *set);  
    3. int sigfillset(sigset_t *set);  
    4. int sigismember(const sigset_t *set, int signum);  
    5. int sigaddset(sigset_t *set, int signum);  
    6. int sigdelset(sigset_t *set, int signum);  

    以上几个函数的用法都是比较简单,这里就不一一介绍,我们通过一个例子来学习其用法:

    [cpp] view plain copy
    1
    1. #include <signal.h>  
    2. #include <stdio.h>  
    3.   
    4. int main(int argc, char *argv[])  
    5. {  
    6.     sigset_t set;   // 定义一个信号集变量  
    7.     int ret = 0;  
    8.   
    9.     sigemptyset(&set); // 清空信号集的内容  
    10.       
    11.     // 判断 SIGINT 是否在信号集 set 里  
    12.     // 在返回 1, 不在返回 0  
    13.     ret = sigismember(&set, SIGINT);  
    14.     if(ret == 0){  
    15.         printf("SIGINT is not a member of set \nret = %d\n", ret);  
    16.     }  
    17.           
    18.     sigaddset(&set, SIGINT); // 把 SIGINT 添加到信号集 set  
    19.     sigaddset(&set, SIGQUIT);// 把 SIGQUIT 添加到信号集 set  
    20.       
    21.     // 判断 SIGINT 是否在信号集 set 里  
    22.     // 在返回 1, 不在返回 0  
    23.     ret = sigismember(&set, SIGINT);  
    24.     if(ret == 1){  
    25.         printf("SIGINT is a member of set \nret = %d\n", ret);  
    26.     }  
    27.       
    28.     sigdelset(&set, SIGQUIT); // 把 SIGQUIT 从信号集 set 移除  
    29.       
    30.     // 判断 SIGQUIT 是否在信号集 set 里  
    31.     // 在返回 1, 不在返回 0  
    32.     ret = sigismember(&set, SIGQUIT);  
    33.     if(ret == 0){  
    34.         printf("SIGQUIT is not a member of set \nret = %d\n", ret);  
    35.     }  
    36.       
    37.     return 0;  
    38. }  

    运行结果如下:



    信号阻塞集(屏蔽集、掩码)

    信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。


    所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。


    我们可以通过 sigprocmask() 修改当前的信号掩码来改变信号的阻塞情况。


    所需头文件:

    #include <signal.h>


    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

    功能:

    检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。

    参数:

    how: 信号阻塞集合的修改方法,有 3 种情况:

    SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。
    SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。
    SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。

    set: 要操作的信号集地址。

    若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。

    oldset: 保存原先信号阻塞集地址

    返回值:

    成功:0,

    失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。


    注意:不能阻塞 SIGKILL 和 SIGSTOP 等信号,但是当 set 参数包含这些信号时 sigprocmask() 不返回错误,只是忽略它们。另外,阻塞 SIGFPE 这样的信号可能导致不可挽回的结果,因为这些信号是由程序错误产生的,忽略它们只能导致程序无法执行而被终止。


    示例代码如下:

    [cpp] view plain copy
    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <unistd.h>  
    4. #include <signal.h>  
    5.   
    6. int main(int argc, char *argv[])  
    7. {  
    8.     sigset_t set; // 信号集合  
    9.     int i = 0;  
    10.       
    11.     sigemptyset(&set); // 清空信号集合  
    12.     sigaddset(&set, SIGINT); // SIGINT 加入 set 集合  
    13.       
    14.     while(1)  
    15.     {  
    16.         // set 集合加入阻塞集,在没有移除前,SIGINT 会被阻塞  
    17.         sigprocmask(SIG_BLOCK, &set, NULL);  
    18.         for(i=0; i<5; i++)  
    19.         {  
    20.             printf("SIGINT signal is blocked\n");  
    21.             sleep(1);  
    22.         }  
    23.           
    24.         // set 集合从阻塞集中移除  
    25.         // 假如 SIGINT 信号在被阻塞时发生了  
    26.         // 此刻,SIGINT 信号立马生效,中断当前进程  
    27.         sigprocmask(SIG_UNBLOCK, &set, NULL);  
    28.         for(i=0; i<5; i++)  
    29.         {  
    30.             printf("SIGINT signal unblocked\n");  
    31.             sleep(1);  
    32.         }  
    33.     }  
    34.       
    35.     return 0;  
    36. }  


    可靠信号的操作

    从 UNIX 系统继承过来的信号(SIGHUP~SIGSYS,前 32 个)都是不可靠信号,不支持排队(多次发送相同的信号, 进程可能只能收到一次,可能会丢失)。


    SIGRTMIN 至 SIGRTMAX 的信号支持排队(发多少次, 就可以收到多少次, 不会丢失),故称为可靠信号。


    可靠信号就是实时信号,非可靠信号就是非实时信号。


    signal() 函数只能提供简单的信号安装操作,使用 signal() 函数处理信号比较简单,只要把要处理的信号和处理函数列出即可。


    signal() 函数主要用于前面 32 种不可靠、非实时信号的处理,并且不支持信号传递信息。


    Linux 提供了功能更强大的 sigaction() 函数,此函数可以用来检查和更改信号处理操作,可以支持可靠、实时信号的处理,并且支持信号传递信息。


    下面我们一起学习其相关函数的使用。


    所需头文件:

    #include <signal.h>


    int sigqueue(pid_t pid, int sig, const union sigval value);

    功能:

    给指定进程发送信号。

    参数:

    pid: 进程号。

    sig: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母)进行相应查看。

    value: 通过信号传递的参数。

    union sigval 类型如下:

    [cpp] view plain copy
    1. union sigval  
    2. {  
    3.     int   sival_int;  
    4.     void *sival_ptr;  
    5. };  

    返回值:

    成功:0

    失败:-1


    int sigaction(int signum,const struct sigaction *act, struct sigaction *oldact );

    功能:

    检查或修改指定信号的设置(或同时执行这两种操作)。

    参数:

    signum:要操作的信号。

    act:   要设置的对信号的新处理方式(设置)。

    oldact:原来对信号的处理方式(设置)。


    如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式(设置)存入 oldact。


    返回值:

    成功:0

    失败:-1


    信号设置结构体:

    [cpp] view plain copy
    1. struct sigaction  
    2. {  
    3.     /*旧的信号处理函数指针*/  
    4.     void (*sa_handler)(int signum) ;  
    5.       
    6.     /*新的信号处理函数指针*/  
    7.     void (*sa_sigaction)(int signum, siginfo_t *info, void *context);  
    8.       
    9.     sigset_t sa_mask;/*信号阻塞集*/  
    10.       
    11.     int sa_flags;/*信号处理的方式*/  
    12. };  


    sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给 sa_sigaction、sa_handler 两者之一赋值,其取值如下:

    SIG_IGN:忽略该信号

    SIG_DFL:执行系统默认动作

    处理函数名:自定义信号处理函数

    sa_mask:信号阻塞集

    sa_flags:用于指定信号处理的行为,它可以是一下值的“按位或”组合:

    SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)

    SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。

    SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。

    SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

    SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

    SA_SIGINFO使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数


    信号处理函数:
    void (*sa_sigaction)( int signum, 
    siginfo_t *info, void *context );
    参数说明:

    signum:信号的编号。
    info:记录信号发送进程信息的结构体,进程信息结构体路径:/usr/include/i386-linux-gnu/bits/siginfo.h,其结构体详情请点此链接
    context:可以赋给指向 ucontext_t 类型的一个对象的指针,以引用在传递信号时被中断的接收进程或线程的上下文,其结构体详情点此链接


    下面我们做这么一个例子,一个进程在发送信号,一个进程在接收信号的发送。

    发送信号示例代码如下:

    [cpp] view plain copy
    1. #include <stdio.h>  
    2. #include <signal.h>  
    3. #include <sys/types.h>  
    4. #include <unistd.h>  
    5.   
    6. /******************************************************* 
    7. *功能:     发 SIGINT 信号及信号携带的值给指定的进程 
    8. *参数:        argv[1]:进程号 
    9.             argv[2]:待发送的值(默认为100) 
    10. *返回值:   0 
    11. ********************************************************/  
    12. int main(int argc, char *argv[])  
    13. {  
    14.     if(argc >= 2)  
    15.     {  
    16.         pid_t pid,pid_self;  
    17.         union sigval tmp;  
    18.   
    19.         pid = atoi(argv[1]); // 进程号  
    20.         if( argc >= 3 )  
    21.         {  
    22.             tmp.sival_int = atoi(argv[2]);  
    23.         }  
    24.         else  
    25.         {  
    26.             tmp.sival_int = 100;  
    27.         }  
    28.           
    29.         // 给进程 pid,发送 SIGINT 信号,并把 tmp 传递过去  
    30.         sigqueue(pid, SIGINT, tmp);  
    31.           
    32.         pid_self = getpid(); // 进程号  
    33.         printf("pid = %d, pid_self = %d\n", pid, pid_self);  
    34.           
    35.     }  
    36.       
    37.     return 0;  
    38. }  

    接收信号示例代码如下:
    [cpp] view plain copy
    1. #include <signal.h>  
    2. #include <stdio.h>  
    3.   
    4. // 信号处理回电函数  
    5. void signal_handler(int signum, siginfo_t *info, void *ptr)  
    6. {  
    7.     printf("signum = %d\n", signum); // 信号编号  
    8.     printf("info->si_pid = %d\n", info->si_pid); // 对方的进程号  
    9.     printf("info->si_sigval = %d\n", info->si_value.sival_int); // 对方传递过来的信息  
    10. }  
    11.   
    12. int main(int argc, char *argv[])  
    13. {  
    14.     struct sigaction act, oact;  
    15.       
    16.     act.sa_sigaction = signal_handler; //指定信号处理回调函数  
    17.     sigemptyset(&act.sa_mask); // 阻塞集为空  
    18.     act.sa_flags = SA_SIGINFO; // 指定调用 signal_handler  
    19.       
    20.     // 注册信号 SIGINT  
    21.     sigaction(SIGINT, &act, &oact);  
    22.       
    23.     while(1)  
    24.     {  
    25.         printf("pid is %d\n", getpid()); // 进程号  
    26.           
    27.         pause(); // 捕获信号,此函数会阻塞  
    28.     }  
    29.       
    30.     return 0;  
    31. }  

    两个终端分别编译代码,一个进程接收,一个进程发送,运行结果如下:



    本教程示例代码下载请点此处。

    展开全文
  • 什么是信号? ...“中断”在我们生活中经常遇到,譬如,我正在房间打游戏,突然送快递来了,把正在玩游戏我给“中断”了,我去签收快递( 处理中断 ),处理完成后,再继续玩我游戏。这里...

    什么是信号?

    信号是 Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

    “中断”在我们生活中经常遇到,譬如,我正在房间里打游戏,突然送快递的来了,把正在玩游戏的我给“中断”了,我去签收快递( 处理中断 ),处理完成后,再继续玩我的游戏。这里我们学习的“信号”就是属于这么一种“中断”。我们在终端上敲“Ctrl+c”,就产生一个“中断”,相当于产生一个信号,接着就会处理这么一个“中断任务”(默认的处理方式为中断当前进程)。

    信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件。

    一个完整的信号周期包括三个部分信号的产生信号在进程中的注册信号在进程中的注销,执行信号处理函数。如下图所示:

    注意:这里信号的产生,注册,注销时信号的内部机制,而不是信号的函数实现。

    Linux 可使用命令:kill -l("l" 为字母),查看相应的信号。

    列表中,编号为 1 ~ 31 的信号为传统 UNIX 支持的信号,是不可靠信号(非实时的),编号为 32 ~ 63 的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。非可靠信号一般都有确定的用途及含义,  可靠信号则可以让用户自定义使用。更多详情,请看《Linux信号列表》。

    信号的产生方式

    1)当用户按某些终端键时,将产生信号。

    终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT,终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT,终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。

    2)硬件异常将产生信号。

    除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。

    3)软件异常将产生信号。

    当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。

    4)调用 kill() 函数将发送信号。

    注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。

    5)运行 kill 命令将发送信号。

    此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程。

    信号的常用操作

    发送信号

    所需头文件:

    #include <sys/types.h>

    #include <signal.h>

    int kill(pid_t pid, int signum);

    功能:给指定进程发送信号。

    注意:使用 kill() 函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。

    参数: pid: 取值有 4 种情况:

               pid > 0: 将信号传送给进程 ID 为pid的进程。

               pid = 0: 将信号传送给当前进程所在进程组中的所有进程。

               pid = -1: 将信号传送给系统内所有的进程。

               pid < -1: 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
     

    signum: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母)进行相应查看。

    返回值:

      成功:0

      失败:-1

    下面为测试代码,本来父子进程各自每隔一秒打印一句话,3 秒后,父进程通过 kill() 函数给子进程发送一个中断信号 SIGINT( 2 号信号),最终,子进程结束,剩下父进程在打印信息:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <signal.h>
     
    int main(int argc, char *argv[])
    {
    	pid_t pid;
    	int i = 0;
     
    	pid = fork(); // 创建进程
    	if( pid < 0 ){ // 出错
    		perror("fork");
    	}
    	
    	if(pid == 0){ // 子进程
    		while(1){
    			printf("I am son\n");
    			sleep(1);
    		}
    	}else if(pid > 0){ // 父进程
    		while(1){
    			printf("I am father\n");
    			sleep(1);
    			
    			i++;
    			if(3 == i){// 3秒后
    				kill(pid, SIGINT); // 给子进程 pid ,发送中断信号 SIGINT
    				// kill(pid, 2); // 等级于kill(pid, SIGINT);
    			}
    		}
    	}
     
    	return 0;
    }
    

    运行结果如下:

     

    等待信号

    所需头文件:

    #include <unistd.h>

    int pause(void);

    功能: 

        等待信号的到来(此函数会阻塞)。将调用进程挂起直至捕捉到信号为止,此函数通常用于判断信号是否已到。

    参数: 无

    返回值: 

        直到捕获到信号才返回 -1,且 errno 被设置成 EINTR。

    测试代码如下:

    #include <unistd.h>
    #include <stdio.h>
     
    int main(int argc, char *argv[])
    {
    	printf("in pause function\n");
    	pause();
    	
    	return 0;
    }
    

    没有产生信号前,进程一直阻塞在 pause() 不会往下执行,假如,我们按“Ctrl+c”,pause() 会捕获到此信号,中断当前进程。

     

    处理信号

    一个进程收到一个信号的时候,可以用如下方法进行处理:

    1)执行系统默认动作

    对大多数信号来说,系统默认动作是用来终止该进程。

    2)忽略此信号

    接收到此信号后没有任何动作。

    3)执行自定义信号处理函数

    用用户定义的信号处理函数处理该信号。

    注意:SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。

    产生一个信号,我们可以让其执行自定义信号处理函数。假如有函数 A, B, C,我们如何确定信号产生后只调用函数 A,而不是函数 B 或 C。这时候,我们需要一种规则规定,信号产生后就调用函数 A,就像交通规则一样,红灯走绿灯行,信号注册函数 signal() 就是做这样的事情。

    所需头文件:

    #include <signal.h>

    typedef void (*sighandler_t)(int);// 回调函数的声明
    sighandler_t signal(int signum,sighandler_t handler);

    功能:

    注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞

    参数:

    signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母)进行相应查看。

    handler: 取值有 3 种情况:

    • SIG_IGN:忽略该信号
    • SIG_DFL:执行系统默认动作
    • 信号处理函数名:自定义信号处理函数 ,如:fun

    回调函数的定义如下:

    void fun(int signo)

    {

        // signo 为触发的信号,为 signal() 第一个参数的值

    }

    注意信号处理函数应该为可重入函数,关于可重入函数的更多详情,请《浅谈可重入函数与不可重入函数》

    返回值:

    成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址(具测试发现只有相同信号的下一次才返回上一次的注册信号处理函数地址)或者是常量SIG_DFL,SIG_IGN之一.如果需要使用此返回值,必须在前面先声明此函数指针的类型

    失败:返回 SIG_ERR

    示例一:

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
     
    // 信号处理函数
    void signal_handler(int signo)
    {
    	if(signo == SIGINT){
    		printf("recv SIGINT\n");
    	}else if(signo == SIGQUIT){
    		printf("recv SIGQUIT\n");
    	}
    }
     
    int main(int argc, char *argv[])
    {
    	printf("wait for SIGINT OR SIGQUIT\n");
    	
    	/* SIGINT: Ctrl+c ; SIGQUIT: Ctrl+\ */
    	// 信号注册函数
    	signal(SIGINT, signal_handler);
    	signal(SIGQUIT, signal_handler);
    	
    	// 等待信号
    	pause();
    	pause();
    	
    	return 0;
    }
    

    在终端里敲“Ctrl+c”或“Ctrl+\”,自动调用其指定好的回调函数 signal_handler():

     

    示例二:

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
     
    // 回调函数的声明
    typedef void (*sighandler_t)(int);
     
    void fun1(int signo)
    {
    	printf("in fun1\n");
    }
     
    void fun2(int signo)
    {
    	printf("in fun2\n");
    }
     
    int main(int argc, char *argv[])
    {
    	sighandler_t previous = NULL;
    	
    	// 第一次返回 NULL
    	previous = signal(SIGINT,fun1); 
    	if(previous == NULL)
    	{
    		printf("return fun addr is NULL\n");
    	}
    	
    	// 下一次返回此信号上一次注册的信号处理函数的地址。
    	previous = signal(SIGINT, fun2);
    	if(previous == fun1)
    	{
    		printf("return fun addr is fun1\n");
    	}
    	
    	// 还是返回 NULL,因为处理的信号变了
    	previous = signal(SIGQUIT,fun1);
    	if(previous == NULL)
    	{
    		printf("return fun addr is NULL\n");
    	}
    	
    	return 0;
    }
    

    运行结果如下:

     

    信号集与信号阻塞集

    信号集

    为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux 系统中引入了信号集(信号的集合)。这个信号集有点类似于我们的 QQ 群,一个个的信号相当于 QQ 群里的一个个好友

    信号集是用来表示多个信号的数据类型(sigset_t),其定义路径为:/usr/include/i386-linux-gnu/bits/sigset.h

    信号集相关的操作主要有如下几个函数:

    #include <signal.h>
    int sigemptyset(sigset_t *set);
    int sigfillset(sigset_t *set);
    int sigismember(const sigset_t *set, int signum);
    int sigaddset(sigset_t *set, int signum);
    int sigdelset(sigset_t *set, int signum);
    

    以上几个函数的用法都是比较简单,这里就不一一介绍,我们通过一个例子来学习其用法:

    #include <signal.h>
    #include <stdio.h>
     
    int main(int argc, char *argv[])
    {
    	sigset_t set;	// 定义一个信号集变量
    	int ret = 0;
     
    	sigemptyset(&set); // 清空信号集的内容
    	
    	// 判断 SIGINT 是否在信号集 set 里
    	// 在返回 1, 不在返回 0
    	ret = sigismember(&set, SIGINT);
    	if(ret == 0){
    		printf("SIGINT is not a member of set \nret = %d\n", ret);
    	}
    		
    	sigaddset(&set, SIGINT); // 把 SIGINT 添加到信号集 set
    	sigaddset(&set, SIGQUIT);// 把 SIGQUIT 添加到信号集 set
    	
    	// 判断 SIGINT 是否在信号集 set 里
    	// 在返回 1, 不在返回 0
    	ret = sigismember(&set, SIGINT);
    	if(ret == 1){
    		printf("SIGINT is a member of set \nret = %d\n", ret);
    	}
    	
    	sigdelset(&set, SIGQUIT); // 把 SIGQUIT 从信号集 set 移除
    	
    	// 判断 SIGQUIT 是否在信号集 set 里
    	// 在返回 1, 不在返回 0
    	ret = sigismember(&set, SIGQUIT);
    	if(ret == 0){
    		printf("SIGQUIT is not a member of set \nret = %d\n", ret);
    	}
    	
    	return 0;
    }
    

    运行结果如下:

     

    信号阻塞集(屏蔽集、掩码)

    信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。

    所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。

    我们可以通过 sigprocmask() 修改当前的信号掩码来改变信号的阻塞情况。

    所需头文件:

    #include <signal.h>

    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

    功能:

    检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。

    参数:

    how: 信号阻塞集合的修改方法,有 3 种情况:

    SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。
    SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。
    SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。如果oldset不为空,那么原来的信号保存到oldset中。

     

    set: 要操作的信号集地址。

        若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。

    oldset: 保存原先信号阻塞集地址

    返回值:

        成功:0,

        失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。

    注意:不能阻塞 SIGKILL 和 SIGSTOP 等信号,但是当 set 参数包含这些信号时 sigprocmask() 不返回错误,只是忽略它们。另外,阻塞 SIGFPE 这样的信号可能导致不可挽回的结果,因为这些信号是由程序错误产生的,忽略它们只能导致程序无法执行而被终止。

    示例代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
     
    int main(int argc, char *argv[])
    {
    	sigset_t set; // 信号集合
    	int i = 0;
    	
    	sigemptyset(&set); // 清空信号集合
    	sigaddset(&set, SIGINT); // SIGINT 加入 set 集合
    	
    	while(1)
    	{
    		// set 集合加入阻塞集,在没有移除前,SIGINT 会被阻塞
    		sigprocmask(SIG_BLOCK, &set, NULL);
    		for(i=0; i<5; i++)
    		{
    			printf("SIGINT signal is blocked\n");
    			sleep(1);
    		}
    		
    		// set 集合从阻塞集中移除
    		// 假如 SIGINT 信号在被阻塞时发生了
    		// 此刻,SIGINT 信号立马生效,中断当前进程
    		sigprocmask(SIG_UNBLOCK, &set, NULL);
    		for(i=0; i<5; i++)
    		{
    			printf("SIGINT signal unblocked\n");
    			sleep(1);
    		}
    	}
    	
    	return 0;
    }
    

     

    可靠信号的操作

    从 UNIX 系统继承过来的信号(SIGHUP~SIGSYS,前 32 个)都是不可靠信号,不支持排队(多次发送相同的信号, 进程可能只能收到一次,可能会丢失)。

    SIGRTMIN 至 SIGRTMAX 的信号支持排队(发多少次, 就可以收到多少次, 不会丢失),故称为可靠信号。

    可靠信号就是实时信号,非可靠信号就是非实时信号。


    signal() 函数只能提供简单的信号安装操作,使用 signal() 函数处理信号比较简单,只要把要处理的信号和处理函数列出即可。

    signal() 函数主要用于前面 32 种不可靠、非实时信号的处理,并且不支持信号传递信息。

    Linux 提供了功能更强大的 sigaction() 函数,此函数可以用来检查和更改信号处理操作,可以支持可靠、实时信号的处理,并且支持信号传递信息。

    下面我们一起学习其相关函数的使用。

    所需头文件:

    #include <signal.h>

    int sigqueue(pid_t pid, int sig, const union sigval value);

    功能:给指定进程发送信号

    参数:

        pid: 进程号。

        sig: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母)进行相应查看。

        value: 通过信号传递的参数。

    union sigval 类型如下:

    union sigval
    {
    	int   sival_int;
    	void *sival_ptr;
    };
    

    返回值:

        成功:0

        失败:-1

    int sigaction(int signum,const struct sigaction *act, struct sigaction *oldact );

    功能:检查或修改指定信号的设置(或同时执行这两种操作)。

    参数:

        signum:要操作的信号。

        act:   要设置的对信号的新处理方式(设置)。

        oldact:原来对信号的处理方式(设置)。

       如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式    (设置)存入 oldact。

    返回值:

        成功:0

        失败:-1

    信号设置结构体:

    struct sigaction
    {
    	/*旧的信号处理函数指针*/
    	void (*sa_handler)(int signum) ;
    	
    	/*新的信号处理函数指针*/
    	void (*sa_sigaction)(int signum, siginfo_t *info, void *context);
    	
    	sigset_t sa_mask;/*信号阻塞集*/
    	
    	int sa_flags;/*信号处理的方式*/
    };
    

    sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给 sa_sigaction、sa_handler 两者之一赋值,其取值如下:

    • SIG_IGN:忽略该信号
    • SIG_DFL:执行系统默认动作
    • 处理函数名:自定义信号处理函数

    sa_flags:用于指定信号处理的行为,它可以是一下值的“按位或”组合:

    • SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)
    • SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
    • SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
    • SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
    • SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
    • SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

    信号处理函数:

    void (*sa_sigaction)( int signum, siginfo_t *info, void *context );
    参数说明:

    signum:信号的编号。

    info:记录信号发送进程信息的结构体,进程信息结构体路径:/usr/include/i386-linux-gnu/bits/siginfo.h

    context:可以赋给指向 ucontext_t 类型的一个对象的指针,以引用在传递信号时被中断的接收进程或线程的上下文

    下面我们做这么一个例子,一个进程在发送信号,一个进程在接收信号的发送。

    发送信号示例代码如下:

    #include <stdio.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <unistd.h>
     
    /*******************************************************
    *功能:     发 SIGINT 信号及信号携带的值给指定的进程
    *参数:		argv[1]:进程号
    			argv[2]:待发送的值(默认为100)
    *返回值:	0
    ********************************************************/
    int main(int argc, char *argv[])
    {
    	if(argc >= 2)
    	{
    		pid_t pid,pid_self;
    		union sigval tmp;
     
    		pid = atoi(argv[1]); // 进程号
    		if( argc >= 3 )
    		{
    			tmp.sival_int = atoi(argv[2]);
    		}
    		else
    		{
    			tmp.sival_int = 100;
    		}
    		
    		// 给进程 pid,发送 SIGINT 信号,并把 tmp 传递过去
    		sigqueue(pid, SIGINT, tmp);
    		
    		pid_self = getpid(); // 进程号
    		printf("pid = %d, pid_self = %d\n", pid, pid_self);
    		
    	}
    	
    	return 0;
    }
    

    接收信号示例代码如下:

    #include <signal.h>
    #include <stdio.h>
     
    // 信号处理回电函数
    void signal_handler(int signum, siginfo_t *info, void *ptr)
    {
    	printf("signum = %d\n", signum); // 信号编号
    	printf("info->si_pid = %d\n", info->si_pid); // 对方的进程号
    	printf("info->si_sigval = %d\n", info->si_value.sival_int); // 对方传递过来的信息
    }
     
    int main(int argc, char *argv[])
    {
    	struct sigaction act, oact;
    	
    	act.sa_sigaction = signal_handler; //指定信号处理回调函数
    	sigemptyset(&act.sa_mask); // 阻塞集为空
    	act.sa_flags = SA_SIGINFO; // 指定调用 signal_handler
    	
    	// 注册信号 SIGINT
    	sigaction(SIGINT, &act, &oact);
    	
    	while(1)
    	{
    		printf("pid is %d\n", getpid()); // 进程号
    		
    		pause(); // 捕获信号,此函数会阻塞
    	}
    	
    	return 0;
    }
    

    两个终端分别编译代码,一个进程接收,一个进程发送,运行结果如下:

     

    展开全文
  • 一、中断下半部-工作队列 ... 设备的中断会打断内核中进程的正常调度和运行,系统对更高吞吐率追求势必要求中断服务程序尽可能地短小精悍。但是,这个良好愿望往往与现实并不吻合。 在大多数真...
  • 通俗地说,前者就是进程运行环境参数,如CPU状态参数(PC,SP,函数返回地址...),内存、文件fd,网络fd,其他IO操作信息等等,内核有一个数据结构tcb(task control bloc)专门来存储这些信息,有操作系统称之...
  • 模式切换不同于进程上下文切换,它进行应该是模式之间上... 1、保存 CPU 寄存器原来用户态指令位 2、为了执行内核态代码,CPU 寄存器需要更新为内核态指令新位置。 3、跳转到内核态运行内核任务。 ...
  • 在Windows下使用pythonsubprocess.Popen创建子进程时,子进程如果是控制台程序(子系统为console程序),便会继承父进程的控制台窗口 当子进程在运行时,如果在控制台使用Ctrl+C向父进程发送信号,这时会让子进程...
  • 在下列事件中,进程可能把控制权交给操作系统: ...在计算机系统中,内核是被信任的第三方,可以在里面做对计算机系统里的任何内容的控制,而且可以执行它的特权指令。这种信任并不是指内核和外界隔...
  • 1、ARM处理器程序运行过程 扩展: 1)、在CPU里面,有一个很重要单元名叫MMU,内存管理单元。 关于内存管理单元,有如下...系统的每一个进程有唯一id。在c语言中,用pid_t类型表示。其实就是一个非负整数。p
  • 进程运行时出现了系统调用或中断事件,转而去执行操作系统内核程序时,称其为处于“核心模式”。 在Linux处理机就具有两种运行状态:核心态和用户态。 Linux下进程概念及特征 (1)、在Linux,把进程定义为...
  • 进程里的几个栈

    千次阅读 2015-04-16 17:12:56
    很多资料上都写分用户栈、内核栈、中断栈,主要问题就是系统...系统调用 :内核态,运行于进程上下文,内核代表进程运行于内核空间   硬件中断 :内核态,运行于中断上下文,内核代表硬件运行于内核空间; 异常  :
  • 系统中出现大量不可中断进程和僵尸进程怎么办?(上) 1. 进程状态 top 和 ps 是最常用查看进程状态工具,我们就从 top 输出开始。下面是一个 top 命令输出示例,S 列(也就是 Status 列)表示进程的状态...
  • 进程调度:Linux里的进程管理调度,如何调度使用不同的进程占用不同的时间片段,主要在核心函数 scheduler_tick (kernel/sched.c) 硬中断触发对操作系统来说,中断是一种电信号,由硬件设备产生,并直接送入中断...
  • 进程是cpu里面东西,多道批处理引进来东西 在时间段内并发 然后真正实现并发其实还算是通道和中断机制出现 中断可以让进程并发,通道可以实现cpu、设备并发 进程(有点向cpu申请资源意味) ...
  • 操作系统复习:进程

    2021-03-24 11:17:28
    操作系统复习:进程一、进程前驱图进程状态切换进程挂起和阻塞区别进程控制块PCB进程切换过程内核功能:中断处理、适中管理、原语操作创建进程的关系进程的通信消息传递方式2.线程总结 一、进程 前驱图 后...
  • Linux操作系统进程的七种状态

    万次阅读 多人点赞 2018-05-07 20:43:06
    1 Linux中进程的七种状态(1)R运行状态(runing):并不意味着进程一定在运行中,也可以在运行队列;(2)S睡眠状态(sleeping):进程在等待事件完成;(浅度睡眠,可以被唤醒)(3)D磁盘睡眠状态(Disk sleep...
  • 操作系统学习笔记-2019 王道考研 操作系统-05-中断和异常 ...之前说多道批处理系统,cpu给每个进程分配了时间片,这个时间片cpu为这个进程服务,如果进程A用完了属于他时间片,操作系统就会切换...
  • 这段时间在看Unix网络编程卷1,在5.9节处理SIGCHLD信号,关于处理僵死进程第四步如下写道:信号是在父进程阻塞于慢系统调用(accept)时由父进程捕获,内核就会使慢系统调用(accept)返回一个EINTR错误。...
  • qt环境程序中通过QProcess启动进程的方式启动windows系统自带画图程序中遇到错误: 通过命令启动画图程序,传给画图程序路径参数要用双引号包含在里面,否则会出现路径被空格字符中断 例如:mspaint.exe g:\...
  • php里进程创建和分析

    2018-05-12 13:18:00
    Linux系统中,进程的执行模式划分为用户模式和内核模式,当进程运行于用户空间时属于用户模式,如果在用户程序运行过程中出现系统调用或者发生中断事件,就要运行操作系统(即核心)程序,进程的运行模式就变为内核模式在该...
  • R运行状态(running):运行状态不代表进程就一定在运行中,它表示进程在运行中或者在运行队列,同一时刻,只会有一个进程在CPU中运行,其他进程都在运行队列等待,我们把运行中的进程和在运行等待 都叫做...
  • linux系统如何将一个信号通知到进程信号种类进程描述符信号发送到进程大致流程linux信号基于轮训还是中断 信号种类 硬件异常产生错误。比如非法访问内存,除数为0… 外部信号。键盘上Ctrl-C产生SGINT信号,...

空空如也

空空如也

1 2 3 4 5 ... 18
收藏数 348
精华内容 139
关键字:

进程里的系统中断