【C++服务器入门基础------3.IPC进程间通信--信号】

大学生寒假在家过于无聊,整理一下以前学过的知识,顺便复习一下,水平较低,专业性差,仅供参考,不喜勿喷(反正也没人看)。本来说一两天一篇,结果这几天连续被叫出去潇洒了一波,现在努力补上。

一、IPC(Inter-Process Communication,进程间通信)有哪些

·信号

·管道(有名管道和匿名管道)

·消息队列

·共享内存

·信号量

·socket套接字

本篇先介绍信号,其他的几个通信方式会在后面陆续介绍。

二、信号的概念

软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。

三、信号列表

列举一些常见的信号

1) SIGHUP
本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。

登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个 Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也能继续下载。

此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。

2) SIGINT
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。

3) SIGQUIT
和SIGINT类似, 但由QUIT字符(通常是Ctrl-\)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。

4) SIGILL
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。

5) SIGTRAP
由断点指令或其它trap指令产生. 由debugger使用。

6) SIGABRT
调用abort函数生成的信号。

7) SIGBUS
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。

8) SIGFPE
在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。

9) SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。

10) SIGUSR1
留给用户使用

11) SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.

12) SIGUSR2
留给用户使用

13) SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。

14) SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.

15) SIGTERM
程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。

17) SIGCHLD
子进程结束时, 父进程会收到这个信号。

如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程来接管)。

18) SIGCONT
让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符

19) SIGSTOP
停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.

20) SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号

21) SIGTTIN
当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.

22) SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.

23) SIGURG
有”紧急”数据或out-of-band数据到达socket时产生.

24) SIGXCPU
超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。

25) SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。

26) SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.

27) SIGPROF
类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.

28) SIGWINCH
窗口大小改变时发出.

29) SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作.

30) SIGPWR
Power failure

31) SIGSYS
非法的系统调用。

四、signal和kill函数

(1)signal函数

如果想让程序能够处理信号,可以使用signal库函数,要引入头文件

原型

void (*signal(int sig, void (*func)(int))) (int);

参数

signal是一个带sig和func两个参数的函数,准备捕捉或屏蔽的信号由参数sig给出,接收到指定信号时将要调用的函数由func给出。

func这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void

func也可以是下面两个特殊值:

SIG_IGN 屏蔽该信号

SIG_DFL 恢复默认行为

(2)kill函数

#include
#include

原型

int kill(pid_t pid, int sig);

参数


pid:可能选择有以下四种

1.pid大于零时,pid是信号欲送往的进程的标识。
2. pid等于零时,信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
3. pid等于-1时,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
4. pid小于-1时,信号将送往以-pid为组标识的进程。

sig:准备发送的信号代码,假如其值为零则没有任何信号送出,但是系统会执行错误 检查,通常会利用sig值为零来检验某个进程是否仍在执行。

(3)代码示例

#include 
#include 
#include 
#include 
#include 
#include 


using namespace std;


void signal_fun(int num);


int main()
{
    //不带数据
    pid_t pid;
    signal(SIGUSR1, signal_fun);//注册信号

    pid = fork();//开启子进程

    if (pid == 0)//子进程
    {   
        cout << "child pid  " << getpid() << endl;
        sleep(1);
        _exit(0);

    }
    else if (pid > 0)//父进程
    {
        cout << "father pid  " << getpid() << endl;
        kill(pid, SIGUSR1);//向子进程发信号
       
    }




    return 0;
}


void signal_fun(int num)//不带数据 num是信号的值
{
    cout << "num=" << num << endl;
}

运行结果

我们先用signal函数注册了信号,随后再调用fork函数开辟子进程,此时无论是父进程还是子进程收到SIGUSR1信号后都会调用signal_fun函数,而在案例中,父进程向子进程发送了信号,子进程调用了signal_fun。

五、不可靠信号和可靠信号

(1)不可靠信号

linux信号机制基本上是从unix系统中继承过来的。把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN的信号都是不可靠信号。早期unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是:

·进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。

·早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。

·linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,linux下的不可靠信号问题主要指的是信号可能丢失。

(2)可靠信号

随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种unix版本分别在这方面进行了研究,力图实现"可靠信 号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。

同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。

六、sigaction和sigqueue函数

(1)sigaction函数

包含头文件

功能

sigaction函数用于改变进程接收到特定信号后的行为。

原型

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

参数

该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)

第二个参数是指向结构sigaction的一个实例的指针,在结构 sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理

第三个参数oldact指向的对象用来保存原来对相应信号 的处理,可指定oldact为null。

返回值:函数成功返回0,失败返回-1

sigaction结构体

第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。

struct sigaction 
{ 
   union
  { 
    __sighandler_t _sa_handler; 
  void (*_sa_sigaction)(int,struct sig_info *, void *); 
}_u 
sigset_t sa_mask; 
unsigned long sa_flags; 
void (*sa_restorer)(void); 
} 

联合数据结构中的两个元素_sa_handler以*_sa_sigaction指定信号关联函数,即用户指定的信号处理函数。除了可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为SIG_IGN(忽略信号)。

由_sa_handler指定的处理函数只有一个参数,即信号值,所以信号不能传递除信号值之外的任何信息;由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个3参数信号处理函数。第一个参数为信号值,第三个参数没有使用(posix没有规范使用该参数的标准),第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值。

(2)sigqueue函数

功能

新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数 sigaction()配合使用。

原型

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

参数

 sigqueue的第一个参数是指定接收信号的进程id,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

返回值成功返回0,失败返回-1

联合数据结构union sigval

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

注意:

·sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组

·siginfo_t 结构体中(sigval_t) si_value就是sigqueue函数中传入的第三个参数sigval

·siginfo_t 结构体中(int) si_int就是从sigqueue函数中传入的第三个参数sigval.sival_int中获得

·siginfo_t 结构体中si_ptr就是从sigqueue函数中传入的第三个参数sigval.sival_ptr中获得

(3)代码示例

#include 
#include 
#include 
#include 


using namespace std;

void signal_fun(int num, siginfo_t* info, void* vo);


int main()
{
   

    //带数据

    pid_t pid;
    //信号传输封装的结构体  包含信号信息

    struct sigaction act;
    act.sa_sigaction = signal_fun;//设置函数
    act.sa_flags = SA_SIGINFO;//通知这个信号带数据
    //注册信号
    sigaction(SIGUSR1, &act, NULL);


    pid = fork();//开启子进程

    if (pid == 0)//子进程
    {     
        cout << "child pid:" << getpid() << endl;
        
        sleep(3);
        _exit(0);

    }
    else if (pid > 0)//父进程
    {
        cout << "father pid:" << getpid() << endl;

        //封装联合体数据
        union sigval sig_value;
        sig_value.sival_int = 2001;//附带一个整形数据2001
        //发送指定信号
        sigqueue(pid, SIGUSR1, sig_value);//向子进程发送信号*/
    }



    return 0;
}

void signal_fun(int num, siginfo_t* info, void* vo)//带数据
{

    int value = info->si_int ;//siginfo_t中的si_int相当于sigval联合体中的sival_int
    cout << "fun1  value=" << value << endl;

}

运行结果

先设置好 struct sigaction act这个变量,然后利用sigaction注册信号,父进程设置好union sigval sig_value 变量,利用sigqueue向子进程发送信号,并且附带了一个整形数据2001。子进程收到信号后调用signal_fun函数,打印数据。 

你可能感兴趣的:(C++服务器入门系列,c++,linux,信号处理,ipc)