- 同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)。
- 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
- 换句话说,就是由调用者主动等待这个调用的结果。
- 而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
信号是一个软件中断。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。
信号的种类:一共有62种
1~31:非可靠信号,信号有可能会丢失。
34~64:可靠信号,信号不会丢失。
Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是信号可能丢失。
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在 支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送 函数kill()。
信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
如何把一个进程放到后台去运行,在启动命令之后加&符号
fg:就将刚刚放到后台的进程,在放到前台来运行
按照原因分类:
#include <signal.h> int kill(pidt pid,int sig); //pid:接受该信号的进程的pid //sig:要发送的具体信号值
谁调用该函数,谁收到6号信号封装了的ki11
#include <stdlib.h> void abort(void); //相当于kill(getpid(),6); 6号信号(SIGABRT)==>(double free) 解引用空指针+内存访问越界==》进程收到11号信号(SIGSEGV)
kill -l //列出当前所有可用信号,可见上图 kill -[信号名称/编号] 对应进程的pid eg: kill -1 2246 对pid为2246的进程发出暂停信号 kill -SIGKILL 2243 杀死进程2243
信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可。
注意:当前操作系统中没有0号信号
当进程收到一个非可靠信号
第一件事情:将非可靠信号对应的比特位更改为1;
第二件事情:添加sigqueue节点到sigqueue队列当中。但是!!!如果在添加sigqueue节点的时候,队列当中已然有了该信号的sigqueue节点,则不添加。
如果进程收到一个可靠信号
第一件事情:在sig位图当中更改该信号对应的比特位为1;
第二件事情:不论之前sigqueue队列当中是否存在该信号的sigqueue节点,都再次添加sigqueue节点到sigqueue队列当中去。
1.将该信号的sigqueue节点从sigqueue队列当中进行出队操作
2.信号在sig位图当中对应的比特位从1置位0
1.将该信号的sigqueue节点从sigqueue队列当中进行出队操作
2.需要在判断sigqueue队列当中是还有相同的sigqueue节点
2.1没有相同的节点
信号在sig位图当中对应的比特位从1置位0
2.2还存在相同的节点
不会更改sig位图当中对应的比特位(保持为1)
1.SIG_DFL:默认处理方式
2.SIG_IGN:忽略处理
SIGCHLD信号——僵尸进程的产生实际上是因为子进程在退出的时候,给父进程发送一个SIGSHILD信号,但是父进程在收到这个信号之后,但是父进程对SIGSHILD信号默认是忽略处理,所以才导致子进程的退出资源未能得以回收,才变成了僵尸进程。
不能对子进程发送9号信号(因为不能杀一个已经死去的人)
3.自定义信号处理方式
①signal函数
#include <signal.h> typedef void (*sighandler_t) (int); sighandler_t signal(int signum,sighandler_t handler); //该函数可以更把signum信号的处理动作更换成 handler所指向的函数 //也就是说函数收到signum信号的时候,不会去执行signum信号, //而是会执行handle所指向的函数
man手册说明:
signal()的行为在UNIX版本之间有所不同,并且在历史上在Linux的不同版本中也有所不同。
避免使用它:改为使用sigaction(2)。请参阅下面的可移植性。
signal()将信号符号的设置为处理程序,该处理程序可以是SIG_IGN,SIG_DFL或程序员定义的函数的地址(“信号处理程序”)。
如果信号值传递给该进程,则发生以下情况之一:
*如果设置为SIG_IGN,则忽略该信号。
*如果设置为SIG_DFL,则发生与信号关联的默认操作(请参见signal(7))。
*如果设置为函数,则首先将处置重置为SIG_DFL,或者阻止信号(请参见下面的可移植性),然后使用参数signum调用处理程序。如果调用处理程序导致信号被阻塞,则从处理程序返回后,信号将被解除阻塞。
信号SIGKILL和SIGSTOP不能够被捕获或忽略。
第一个参数指定信号的值,第二个参数是一个函数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。
如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。
#include <stdio.h> #include <unistd.h> #include <signal.h> void sigcallback(int signo) { printf("signo : %d\n", signo); } int main() { signal(2, sigcallback); signal(20, sigcallback); while(1) { printf("linux so easy\n"); sleep(1); } return 0; }
②sigaction函数
#include <signal.h> int sigaction( int signum, const struct sigaction *act,struct sigaction *oldact);
man手册说明:
sigaction()系统调用用于更改进程在收到特定信号后采取的操作。 (有关信号的概述,请参见signal(7))
signum指定信号,并且可以是除SIGKILL和SIGSTOP之外的任何有效信号。
如果act为非NULL,则从act安装信号信号的新动作。 如果oldact为非NULL,则先前的操作将保存在oldact中。
siginfo_t*是指向siginfo_t结构的指针,结构中包含信号携带的数据值。
signal函数底层也是调用sigaction函数。
在使用 struct sigaciton之前,要先进行初始化——用sigempty函数。
int sigemptyset(sigset_t*set);/将位图的所有比特位设置为0 //第一次按下ctrl+|之后执行sigcallback //第二次按下ctrl+|之后执行三号信号,程序退出 #include <stdio.h> #include <unistd.h> #include <signal.h> void sigcallback(int signo) { printf("signo : %d\n", signo); } int main() { //act --》 入参 struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = sigcallback; //oldact --》 出参 struct sigaction oldact; sigaction(3, &act, &oldact); getchar(); sigaction(3, &oldact, NULL);//复原 while(1) { printf("linux so easy\n"); sleep(1); } return 0; }
什么时候进入到内核空间:调用系统调用函数的时候,或者调用库函数的时候。(库函数底层大多数都是封装系统调用函数的)
信号的阻塞,并不会干扰信号的注册!信号该注册还是注册的,只不过当前的进程不能立即处理了
2.1 当我们将block位图当中对应信号的bite为置为1,表示当前进程阻塞该信号
2.2 当进程收到一个该信号的时候,进程还是一如既往的对该信号进行注册
2.3 当进程进入到内核空间,准备返回用户空间的时候,调用do_signal函数,不会立即去处理该信号
3. 这里面的不会立即处理,一定不是之后不处理(解除阻塞的时候就会对其进行处理)。
sigprocmask函数——设定对信号屏蔽集内的信号的处理方式(阻塞或不阻塞)
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
block(new)=block(old)| set (按位或)
0010 = 0000 | 0010
block(new)=block(old)&(~set)(取反之后按位与)
0000 = 0010 & (~0010)=0010 & 1101
block(new)=seto1dset:原来的阻塞位图
注意:9号信号不能被阻塞,也不可以对9号信号进行自定义函数
#include <stdio.h> #include <unistd.h> #include <signal.h> void sigcallback(int signo) { printf("signo : %d\n", signo); } int main() { signal(2, sigcallback); signal(40, sigcallback); sigset_t set; sigfillset(&set); //将参数set信号集初始化,然后把所有的信号加入到此信号集里即将所有的信号标志 //位置为1,阻塞所有的信号 sigset_t oldset;//保存之前的信号集 sigprocmask(SIG_SETMASK, &set, &oldset); getchar(); sigprocmask(SIG_SETMASK, &oldset, NULL);//信号集复原,解除上面的阻塞 while(1) { printf("linux so easy\n"); sleep(1); } return 0; }