万字详解linux系列进程信号(代码片段)

山舟 山舟     2022-12-03     152

关键词:

文章目录


一、信号简介

这部分先对信号做一个整体的介绍,更详细的内容会在后文讲解。

1.查看信号

通过kill -l查看全部的信号。


本文主要讲解普通信号,简单介绍实时信号。

man 7 signal可以查看信号在什么条件下产生、默认的处理动作是什么……


2.信号的本质

上图中的信号本质是一个宏,可以如下查看某个信号对应的宏的值。

上图中有三个头文件中包含了SIGQUIT(其实也包含了全部的信号),下面随便打开一个头文件查看其中的宏(部分宏如下)。


3.信号的记录和发送

信号收到时,不一定要立刻处理,比如当前有更重要的任务要执行,就可以先暂时搁置信号,但是要记录下这个信号需要在未来“合适”的时间处理掉,所以信号需要用一段内容来记录。

信号记录在进程的task_struct(PCB)结构体中,对于普通信号,本质是记录多个信号“是否”产生。由于只有“是”或“否”两种状态,且普通信号编号是1-31,显然用一个位图(这里用无符号整型即可)的32个比特位即可很好的管理(比特位的位置代表是否收到信号,比特位为0代表没有收到了信号,为1则代表收到了信号)。

进程收到信号,本质是位图被修改,只能通过操作系统修改进程内的信号位图(虽然可以通过命令行或代码向某个进程发送信号,但其实还通过了操作系统,所以本质还是操作系统)。


4.从键盘输入的信号

执行如下死循环代码:

#include <stdio.h>
#include <unistd.h>

int main()

	while (1)
	
		printf("hello signal!\\n");
		sleep(1);
	
	return 0;

对于这种死循环的进程,需要从键盘输入Ctrl+C来终止,而这本身也是一种信号。


这里要注意:Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像Ctrl+C这种控制键产生的信号。

在运行进程的命令后加&即可将进程放到后台运行,这时Shell不必等待进程结束就可以接受新的命令,启动新的进程,但是后台进程无法用Ctrl+C结束,可以用kill -3来终止。


上面的动图中,后台进程用Ctrl+C无法终止,这时要通过kill -3再加上后台进程的PID来终止。


5.signal自定义信号

为什么通过Ctrl+C可以终止一个进程呢?它本质还是向进程发送了一个编号为2的SIGINT信号,只不过这个信号是通过键盘输入的,然后经过操作系统处理后再发送给进程。

SIGINT原本是用来结束进程的,但用signal自定义它的功能后就可以使它对进程的效果改变。


下面的代码就通过signal函数自定义了2号信号的功能,我这里是当输入2号信号(Ctrl+C)时打印"new function!",而系统原来定义的终止进程的功能不再起作用。

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

//handle函数内实现的功能是打印"new function!"
void handle(int sigNum)

	printf("new function! sigNum:%d\\n", sigNum);


int main()

	signal(2, handle);//将2号信号的功能改为handle
	while (1)
	
		printf("a\\n");
		sleep(1);
	
	return 0;

可以看到,每次按下Ctrl+C,都会打印对应内容(实现自定义的功能而不是像原来一样终止进程),而sigNum也证明Ctrl+C对应的信号值确实是2号。


但不是所有的信号都可以被自定义(捕捉)的,比如9号信号SIGKILL。


6.处理信号的一般方式

  • 1.忽略此信号。
  • 2.执行该信号的默认处理动作(系统中已经定义好的)。
  • 3.提供一个信号处理函数(像上面那样自定义的函数),要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号。

二、信号产生

1.通过终端按键(键盘)产生信号

介绍两个常用的键盘输入产生的信号:Ctrl+C发送2号信号SIGINT、Ctrl+\\发送3号信号SIGQUIT。(注意键盘输入的信号不止这两个,这里只是以这两个为例)

SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump


Core Dump(核心转储)

当一个进程异常终止时,可以把进程的核心数据全部转而储存到磁盘上,文件名通常是core.PID,这一现象叫做Core Dump

一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,泄漏会导致不安全。

如下图中所示云服务器环境中一般不允许产生core文件(core file size的值为0)。

通过ulimit -c修改core file size后,再次用Ctrl+\\发送信号,可以看到进程被终止且产生了core文件。


2.程序异常

程序异常时操作系统会向程序发送信号来终止进程,这一结果可以通过core文件来看到,下面通过用core文件进行事后调试来讲解上述过程。

事后调试

进程异常终止通常是因为有Bug(比如非法内存访问导致段错误),事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。

通过下面的代码(有除零错误)来大致演示通过core文件调试。

#include <stdio.h>
#include <unistd.h>

int main()

	while (1)
	
		sleep(1);
		int a = 1 / 0;//除零错误
	
	return 0;

运行可执行程序及后续调试如下:

可以看到core文件中包含的调试信息非常详细、精准,如果代码量极大,那么core文件会更加快速地调试。

而上面的Floating point exception报错和core中收到的8号信号SIGFPE刚好对应。


3.调用函数

(1)kill函数


mykill.c如下,通过它产生可执行程序kill并传入命令行参数来终止其他进程。

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

int main(int argc, char* argv[])

	//argv[0]是执行可执行程序
	//argv[1]是进程的pid
	//argv[2]是发送的信号编号
	//argv[3]是NULL
	//argc是有效参数3
	if (argc != 3)
	
		printf("input error!\\n");
		exit(1);
	
	else
	
		pid_t pid = atoi(argv[1]);
		int sigNo = atoi(argv[2]);

		kill(pid, sigNo);
	
	return 0;

mytest.c如下,一个简单的死循环,等待可执行程序kill发送信号将其终止。

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

int main()

	while (1)
	
		printf("a\\n");
		sleep(1);
	
	return 0;

运行test死循环后用可执行程序kill传入test的pid和信号编号来向test发送信号,注意不是使用的命令行中的kill,而是用的自己实现的可执行程序。


(2)raise函数

raise是自己给自己发送信号,与kill不同。

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

//处理传入的信号编号
void handler(int signo)

	printf("get a signal : %d\\n", signo);


int main()

	signal(2, handler);//将2号信号自定义

	while (1)
	
		printf("I am a process, pid : %d\\n", getpid());
		sleep(1);
		raise(2);//该进程自己给自己发送2号信号
	
	return 0;

可以看拿到,每次该进程自己向自己发送信号,就输出自定义后的内容。


(3)abort

abort使当前进程接收到信号而异常终止。

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

void handler(int signo)

	printf("get a signal : %d\\n", signo);


int main()

	signal(6, handler);

	while (1)
	
		printf("I am a process, pid : %d\\n", getpid());
		sleep(1);
		abort();
	
	return 0;

可以看到,运行到abort后收到6号信号,然后程序就终止了;


4.由于软件条件产生信号

(1)SIGPIPE

SIGPIPE就是一种因软件条件产生的信号,在【万字详解Linux系列】进程间通信(IPC) 中已经介绍过,这里不多赘述。


(2)SIGALRM

触发SIGALRM信号需要用到alarm函数,函数介绍如下:

函数的返回值是0或是之前设定的闹钟时间还余下的秒数。

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

int main()

	int count = 0;
	alarm(1);//倒计时1s
	while (1)
	
		printf("count : %d\\n", count++);//看看1s内可以将一个整型值加到多少
	
	return 0;

可以看到1s的定时内,count被加到了17482,之后Alarm clock也说明进程收到了SIGALRM的信号。


5.硬件异常产生信号

发生硬件异常时,它被硬件以某种方式检测到并通知内核,然后内核向当前进程发送适当的信号。

例如当前进程执行了除零的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号,并将该信号发送给进程。
再如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号,并将该信号发送给进程。


三、信号阻塞

1.相关概念

  • 实际执行信号的处理动作称为信号递达(Delivery)。
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block)某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

注意:阻塞和忽略是不同的,阻塞本质是不让信号递达(直到解除阻塞),而忽略本质是递达(处理信号)的一种方式。


2.内核中的表示

  • 如上,每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。

  • 信号产生时,内核在进程控制块中设置该信号的未决标志(pending),直到信号递达才清除该标志。之后如果被阻塞,则直到阻塞结束才处理,否则直接调用handler内的方法处理该信号。

在上图的例子中:

  1. SIGHUP信号未阻塞也未产生过,如果它递达则执行默认处理动作。
  2. SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号。
  3. SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。

注意:在Linux下如果常规信号在递达之前产生多次则只记最后一次,而实时信号在递达之前产生多次可以依次放在一个队列里。但本篇暂不讨论实时信号。


3.sigset_t

从上图来看,每个信号只有一个比特位的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志同理。
因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些比特位则是与操作系统有关的(所以不能冒然用按位与、按位或来拿到信号,因为操作系统底层不一定是这么实现的)。

从使用者的角度是不必关心的,使用者只能调用下面介绍的函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t,这是毫无意义的。


4.信号集操作函数

(1)对信号集的操作

常用的信号集操作函数有如下五个,各自的大致用法已注释在其后。

#include <signal.h>//头文件

int sigemptyset(sigset_t *set);//初始化set所指向的信号集,使其中所有信号的比特位清零,表示该信号集不包含任何有效信号
int sigfillset(sigset_t *set);//初始化set所指向的信号集,使其中所有信号的对应比特位置为1,表示该信号集的有效信号包括系统支持的所有信号
int sigaddset (sigset_t *set, int signo);//向信号集set中添加编号为signo的信号
int sigdelset(sigset_t *set, int signo);//从信号集set中删除编号为signo的信号
int sigismember(const sigset_t *set, int signo);//判断编号为signo的信号是否在信号集set中

这四个函数都是成功返回0、出错返回-1。sigismember用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

注意,上面五个函数都是系统级别的函数,它们在使用时并不会影响进程的任何信息(不会修改进程的PCB)。


(2)sigpromask

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集,即上面的block表)。

how有三个选项(下面的式子中假设mask是当前的信号屏蔽字,且这些式子只是帮助理解,不能实际写在代码中)

  1. SIG_BLOCK:这时传入的set包含了希望添加到当前信号屏蔽字(block表)中的信号,可以理解为mask=mask | set。
  2. SIG_UNBLOCK:这时传入的set包含了希望从当前信号屏蔽字(block表)中取消阻塞的信号,可以理解为mask=mask & ~set。
  3. SIG_SETMASK:设置当前的信号屏蔽字为传入的set,相当于mask=set。

set就是传入一个信号集,然后根据how的选项来进行操作。

oset是输出型参数,传入NULL时不做处理,如果传入非空则将原来的信号屏蔽字返回至oset,也就是拿到了修改之前的信号屏蔽字。


三个参数的功能整理如下:

  • 如果oset是非空指针,则当前进程的信号屏蔽字通过oset参数传出。
  • 如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
  • 如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。

(3)sigpending

这个函数非常简单,就是获取当前进程的pending位图。


(4)使用函数

下面的代码的大致功能是首先阻塞2号信号SIGINT,然后不断打印pending表(这时应该是全0),当向该进程发送2号信号后会观察到pending表第二位变为1(被阻塞而无法递达)。

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

void printPending(sigset_t *pending)

    //依次打印31个信号
    int i = 1;
    for (i = 1; i <= 31; i++)
    
        //用sigismember判断第i个信号是否存在于pending表中
        if (sigismember(pending, i))
            printf("1");//存在打印1
        else
            printf("0");//不存在打印0
    
    printf("\\n");


int main()

    sigset_t set, oset;

    //初始化
    sigemptyset(&set);
    sigemptyset(&oset);
   
    sigaddset(&set, 2);//将2号信号加入set信号集
    sigprocmask(SIG_SETMASK, &set, &oset);//将2号信号加入信号屏蔽字,并将原来的信号集返回至oset
   
    sigset_t pending;//保存pending表
    while (1)
    
        sigemptyset(&pending);//初始化
        sigpending(&pending);//拿到pending表

        printPending(&pending);//打印pending表
        sleep(1);//每隔一秒打印一次
    
    return 0;

运行如下:


最后通过Ctrl+\\发送信号终止进程成功了,因为这个信号没有被阻塞;但由于Ctrl+C发送的信号被阻塞,所以无法终止进程。


对上面的程序略加修改,使2号信号在阻塞5s后递达给进程,结果是终止进程。

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

void printPending(sigset_t *pending)

    //依次打印31个信号
    int i = 1;
    for (i = 1; i <= 31; i++)
    
        //用sigismember判断第i个信号是否存在于pending表中
        if (sigismember(pending, i))
            printf("1");//存在打印1
        else
            printf("0");//不存在打印0
    
    printf("\\n");


int main()

    sigset_t set, oset;

    //初始化
    sigemptyset(&set);
    sigemptyset(&oset);
   
    sigaddset(&set, 2);//将2号信号加入set信号集
    sigprocmask(SIG_SETMASK, &set, &oset);//将2号信号加入信号屏蔽字,并将原来的信号集返回至oset
   
    sigset_t pending;//保存pending表
    int count = 0;
    while (1)
    
        sigemptyset(&pending);//初始化
        sigpending(&pending);//拿到pending表

        printPending(&pending);//打印pending表
        sleep(1);
        count++;
        if (count >= 5)//5秒后解除阻塞
        
            //恢复刚开始的信号屏蔽字(2号信号被解除阻塞)
            sigprocmask(SIG_SETMASK, &oset, NULL);
            //恢复信号屏蔽字后,2号信号不再被阻塞,而是递达给进程
            //于是进程被终止
        
    
    return 0;

现象如下,打印两次后向进程发送2号信号SIGINT,又打印三次后解除阻塞,信号递达,进程终止。


四、内核态和用户态

进程收到信号后不是立即对其进行处理的,而是在“合适”的时候,这个“合适”的时候指的是从内核态切换回用户态时。那么什么是内核态和用户态,下面进行介绍。

内核态通常执行OS的代码,是一种权限非常高的状态。用户态通常执行普通用户的代码,是一种受监管的普通状态。


五、信号处理的完整过程

了解了上面的相关知识后,就不难理解下面信号处理的完整过程了,具体见下图。


如果忽略收到的信号或是按照默认处理动作处理时,整个过程比较简单(因为没有设计到多次设计状态的切换),而处理自定义的动作叫做信号捕捉,下面讲到的就是信号捕捉。


六、信号捕捉

sigaction

  • sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体。
  • 将sa_handler赋值为常数SIG_IGN表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数。该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。
  • 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项,后面的代码都把sa_flags设为0;sa_sigaction是实时信号的处理函数,本篇暂不详细解释这两个字段。

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。


运行下面的代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

struct sigaction act, oact;//两个传入sigaction的结构体

void handler(int signo)

	printf("get a signal : %d\\n", signo);
	sigaction(SIGINT, &oact, NULL);//将SIGINT的处理动作改为最开始的(系统默认的)


int main()

	//初始化
	memset(&act, 0, sizeof(act));
	memset(&oact, 0, sizeof(oact));
	
	act.sa_handler = handler;//捕捉信号
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);//初始化

	sigaction(SIGINT, &act, &oact);//传入act修改SIGINT的处理动作

	while (1)
	
		printf("hello world!\\n");
		sleep(1);
	
	return 0;

现象如下:

<

万字详解linux系列进程间通信(ipc)(代码片段)

文章目录一、进程间通信1.目的2.如何通信3.分类二、管道1.概念2.匿名管道(1)实现父子进程间通信(2)fork角度的理解(3)文件描述符角度的理解(4)匿名管道特点(5)四种情况(6&#x... 查看详情

linux进程信号万字详解(上)(代码片段)

🎇Linux:博客主页:一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题博主的能力有限,出现错误希望大家不吝赐教分享给大家一句我很喜欢的话:看似不起波澜的日复一日,一定会在某一天... 查看详情

linux从青铜到王者第十三篇:linux多线程四万字详解(代码片段)

系列文章目录文章目录系列文章目录前言一、Linux线程概念1.什么是线程2.线程的优点3.线程的缺点4.线程的异常5.线程的用途二、进程和线程的对比1.进程和线程2.多进程的应用场景有哪些?三、线程控制1.POSIX线程库2.创建线程... 查看详情

万字详解linux系列多线程(上)(代码片段)

...容过多,所以分为两篇来写,下篇传送门:【万字详解Linux系列】多线程(下)。一、线程1.概念在一个程序里的一个执行路线就叫做线程(thread)。更 查看详情

万字详解linux系列多线程(下)(代码片段)

...指正前言由于多线程部分内容过多,所以本文接着【万字详解Linux系列】多线程(上)向后介绍多线程相关的内容。一、线程同步1.概念在保证数据安全的前提下,让线程按照某种特定的顺序访问临界资源,从... 查看详情

linux进程详解——万字总结,复习利器(代码片段)

这里写目录标题一、图解计算机体系结构(1)底层硬件(2)驱动程序(3)操作系统(4)SystemCall(系统调用)(5)用户操作接口(6)用户层二、进程1、什么是进程2、创建... 查看详情

万字详解linux系列基础io(代码片段)

文章目录前言(1)当前目录(2)stdin、stdout、stderr一、open(1)标志位(2)O_WRONLY(3)O_CREAT二、close,read,write三、文件描述符1.概念2.原理3.分配规则四、重定向1.输出 查看详情

万字详解linux系列基础io(代码片段)

文章目录前言(1)当前目录(2)stdin、stdout、stderr一、open(1)标志位(2)O_WRONLY(3)O_CREAT二、close,read,write三、文件描述符1.概念2.原理3.分配规则四、重定向1.输出 查看详情

万字详解linux系列文件系统动静态库(代码片段)

文章目录一、文件系统1.inode2.磁盘3.目录4.软硬链接(1)软链接(2)硬链接5.文件的三个时间信息二、动静态库1.基本原理2.认识库3.动静态加载的优缺点静态加载动态加载感谢阅读,如有错误请批评指正一、文... 查看详情

linux系列signal函数详解(代码片段)

Date:2023.1.18文章目录1、介绍2、如何安装多个处理函数3、信号列表转载自:http://imhuchao.com/2300.htmlsignal作用是为信号注册一个处理器。这里的“信号”是软中断信号,这种信号来源主要有三种:程序错误:比如除0&#... 查看详情

[linux]详解linux进程信号(代码片段)

目录1.信号概念2.信号的种类3.信号的处理流程4.信号的产生方式4.1软件产生4.2硬件产生5.信号的注册6.信号的注销7.信号的处理方式7.1信号的处理方式及基本概念7.2信号自定义处理方式8.信号的阻塞8.1block位图8.2接口函数设置阻塞状... 查看详情

linux---信号详解(代码片段)

信号信号概念查看信号信号概念注意:信号处理方式概览产生信号键盘组成产生的信号信号获取进程崩溃的解释硬件异常产生信号coredump查看核心转储除0异常野指针异常栈溢出异常测试不同种类的键盘组合对应的是哪种信号... 查看详情

深入详解linux进程间通信之共享内存(sharedmemory)+信号量同步(代码片段)

在Linux下的多个进程间的通信机制叫做IPC(Inter-ProcessCommunication),它是多个进程之间相互沟通的一种方法。专栏前面的文章中演示过使用sharedmemory进行IPC的方法。本文将更进一步探讨Linux进程间通过共享内容进行通信的方法,包... 查看详情

深入详解linux进程间通信之共享内存(sharedmemory)+信号量同步(代码片段)

在Linux下的多个进程间的通信机制叫做IPC(Inter-ProcessCommunication),它是多个进程之间相互沟通的一种方法。专栏前面的文章中演示过使用sharedmemory进行IPC的方法。本文将更进一步探讨Linux进程间通过共享内容进行通信的方法,包... 查看详情

ps详解(每周一个linux命令系列)(代码片段)

(5)ps详解(每周一个linux命令系列)linux命令ps详解引言:今天的命令是用来看进程状态的ps命令ps我们先看manpsps-reportasnapshotofthecurrentprocesses.翻译:显示当前进程的快照。ps是ProcessStatus的缩写具体的描述如下:DESCRIPTIONpsdisplaysinformatio... 查看详情

linux进阶|万字详解docker镜像的制作,手把手学会!(代码片段)

  创作不易,来了的客官点点关注,收藏,订阅一键三连❤😜  前言运维之基础——Linux。我是一个即将毕业的大学生,超超。如果你也在学习Linux,不妨跟着萌新超超一起学习Linux,拿下Linux,一... 查看详情

万字长文详解yolov1-v5系列模型(代码片段)

一,YOLOv1Abstract1.Introduction2.UnifiedDetectron2.1.NetworkDesign2.2Training2.4.Inferences4.1ComparisontoOtherReal-TimeSystems5,代码实现思考二,YOLOv2摘要YOLOv2的改进1,中心坐标位置预测的改进2,1个gird只能对应一个目标的改进3,backbone的改进4, 查看详情

linux僵尸进程详解(代码片段)

文章目录僵尸进程概念僵尸进程问题及危害实现一个僵尸进程僵尸进程处理僵尸进程的预防(1)通过信号机制(2)Fork两次僵尸进程概念在unix/linux中,正常情况下,子进程是通过父进程创建的。子进程的... 查看详情