嵌入式学习笔记--Linux系统编程--DAY04进程间通信-信号

0.什么是进程间通信

IPC:inter process communication。

进程一般是单独执行在内存空间的,不同的进程各自完成资源的申请、管理和使用。不同的进程之间存在【进程隔离】

有些程序需要不同的进程之间进行相互配合来实现相应的功能,这个时候就需要进程间进行通信。

进程间通信的方式:

1.信号的概念

引入:

路上的红绿灯:红灯停,绿灯性。或者是手机响了,就知道电话来了。

信号在生活中已经遍布各个层面,同样遍布物联网技术。

信号不传递数据,只是一个标志,用来通知进程系统中发生了某种类型的事。

特点:

1.信号是在软件层对中断机制(中断是硬件层)的一种模拟,是一种异步通信方式。

2.信号可以在用户态内核进程之间交互。

信号周期:

一个完整的信号周期包含以下几个部分

1.信号的产生

2.信号的注册与注销

3.信号的处理

信号的编号:

显示信号列表:kill -l

不存在编号为 0 的信号。其中 1-31 号信号称之为常规信号(也叫普通信号或标准信号),34-64 称之为实时信号,驱动编程与硬件相关。名字上区别不大。而前32 个名字各不相同。

信号4要素:

 1)编号

2)名称

3)事件

4)默认处理动作

可通过 man 7 signal 查看帮助文档获取

注意:9号和19号新号不允许被忽略和被用户捕捉(因为是直接发送给内核来杀死进程)

2.信号的产生与处理

a) 当用户按某些终端键时,将产生信号。 终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT 终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT 终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。

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

c) 软件异常将产生信号。 当检测到某种软件条件已发生(如:定时器 alarm),并将其通知有关进程时,产生信号。

d) 调用系统函数(如:kill、raise、abort)将发送信号。注意:接收信号进程和发送信号进程
的所有者必须相同
,或发送信号进程的所有者必须是超级用户

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

3.未决信号集与信号阻塞集

Linux 内核的进程控制块 PCB 是一个结构体,task_struct, 除了包含进程 id,状态,工作目录,用户 id,组 id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集未决信号集

未决信号集:当前信号还没有执行

阻塞信号集:当前信号因为某种原因处于阻塞状态。(将某些信号加入集合,对他们设置屏蔽,当屏蔽 x 信号后,再收到该信号,该信号的处理将推后(处理发生在解除屏蔽后)。

4.相关API函数

kill函数,给指定的进程发送信号(自杀)

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

函数功能:给指定的进程发送指定信号(不一定要杀死,有的信号不是结束进程信号)

参数:pid取值有4种情况

pid>0:将信号传送给指定D的id

pid =0:将信号发给当前进程所在进程组所有进程

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

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

sig:可以是编号,也可以是宏(不推荐用编号,推荐用宏)

返回值:

成功为0,失败为-1

代码案例:

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

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

    pid_t pd = fork();

    if (pd == 0) // 子进程
    {
        while (1)
        {
            printf("进程%u在玩游戏中~\n", getpid());
            sleep(1);
        }
        exit(-1);
    }

    else if (pd > 0) // 父进程
    {

        printf("5s后结束玩游戏\n");
        sleep(5);
        kill(pd, SIGKILL);
        wait(NULL);
    }

    return 0;
}

raise函数:给自己发送信号(相当于自杀)

#include <signal.h>
int raise(int sig);

功能:给当前进程发送指定信号(自己给自己发),等价于 kill(getpid(), sig)
参数:sig:信号编号
返回值:
成功:0
失败:非 0 值

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

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

    pid_t pd = fork();

    if (pd == 0) // 子进程
    {
        int count = 0;
        while (1)
        {

            printf("进程%u在玩游戏中~\n", getpid());
            sleep(1);
            count++;
            if (count >5)
            {
                raise(SIGKILL);
               // count = 0;
            }
            
        }
        exit(-1);
    }

    else if (pd > 0) // 父进程
    {

        printf("5s后结束玩游戏\n");
       
        wait(NULL);
    }

    return 0;
}

abort函数

#include <stdlib.h>
void abort(void);
/*功能:给自己发送异常终止信号 6) SIGABRT,并产生 core 文件,等价于 kill(getpid
(), SIGABRT);
参数:无
返回值:无*/

alarm函数(定时杀)

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
/*功能:
设置定时器(闹钟)。在指定 seconds 后,内核会给当前进程发送 14)SIGALRM 信
号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器。
取消定时器 alarm(0),返回旧闹钟余下秒数。
参数:
seconds:指定的时间,以秒为单位
返回值:
返回 0 或剩余的秒数 */

代码案例:

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

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

    pid_t pd = fork();

    if (pd == 0) // 子进程
    {
        int count = 0;
        alarm(5);
        while (1)
        {
            printf("进程%u在玩游戏中~\n", getpid());
            sleep(1);
        }
        exit(-1);
    }

    else if (pd > 0) // 父进程
    {

        printf("5s后结束玩游戏\n");

        wait(NULL);
    }

    return 0;
}

setitimer函数

#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itim
erval *old_value);


函数功能:

设置定时器(闹钟)。 可代替 alarm 函数。精度微秒 us,可以实现周期定时。
参数:
which:指定定时方式
a) 自然定时:ITIMER_REAL → 14)SIGALRM 计算自然时间
b) 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进
程占用 cpu 的时间
c) 运行时计时(用户 + 内核):ITIMER_PROF → 27)SIGPROF 计算占用 cpu 及
执行系统调用的时间
new_value:struct itimerval, 负责设定 timeout 时间
struct itimerval {
struct timerval it_interval; //闹钟触发周期(后续的每次执行时间)
struct timerval it_value; //闹钟触发时间(第一次定时的时间)
};
struct timeval {
long tv_sec; //秒
long tv_usec; //微秒
}
itimerval.it_value: 设定第一次执行 function 所延迟的秒数
itimerval.it_interval: 设定以后每几秒执行 function

old_value: 存放旧的 timeout 值,一般指定为 NULL
返回值:
成功:0
失败:-1

代码案例:

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




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

    struct itimerval itim;
    itim.it_interval.tv_sec = 5;
    itim.it_interval.tv_usec = 0;
    itim.it_value.tv_sec = 1;
    itim.it_value.tv_usec = 0;

    setitimer(ITIMER_REAL,&itim,NULL);
    while (1)
    {
        printf("111\n");
        //sleep(1);
    }
    

    return 0;
}

5.给信号注册自定义函数

一个进程收到一个信号的时候,可以用如下方法进行处理:
1)执行系统默认动作 对大多数信号来说,系统默认动作是用来终止该进程。
2)忽略此信号(丢弃) 接收到此信号后没有任何动作。

3)执行自定义信号处理函数(捕获) 用用户定义的信号处理函数处理该信号。

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

我们主要关注自定义处理函数。

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:执行系统默认动作
信号处理函数名:自定义信号处理函数,如:func
回调函数的定义如下:

void func(int signo)
{
// signo为触发的信号,为 signal()第一个参数的值 ,需要知道是谁触发了这个信号
}


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

代码案例:

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

char *p = NULL;
void my_deal(int signal)
{
    if (p!=NULL)
    {
        free(p);
        p = NULL;
    }
    printf("p释放掉了\n");
     exit(-1);
}

int main(int argc, char const *argv[])
{
   
    p = (char *)calloc(1, 16);
    strcpy(p, "hello world");
    signal(2,my_deal);

    while (1)
    {
        printf("%s\n", p);
        sleep(1);
    }

    
    free(p);

    return 0;
}

signal因为不通用,建议少用

sigaction函数

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

功能:
检查或修改指定信号的设置(或同时执行这两种操作)。
参数:
signum:要操作的信号。
act: 要设置的对信号的新处理方式(传入参数)。
oldact:原来对信号的处理方式(传出参数)。
如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非
空,则系统将此前指定信号的处理方式存入 oldact。
返回值:
成功:0
失败:-1

struct sigaction 结构体:
struct sigaction {
void(*sa_handler)(int); //旧的信号处理函数指针
void(*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理函数指针
sigset_t sa_mask; //信号阻塞集
int sa_flags; //信号处理的方式
void(*sa_restorer)(void); //已弃用
};
1) sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一
样,应根据情况给 sa_sigaction、sa_handler 两者之一赋值,其取值如下:

a)SIG_IGN:忽略该信号

b)SIG_DFL:执行系统默认动作

c)处理函数名:自定义信号处理函数
2) sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的
信号。 
3) sa_flags:用于指定信号处理的行为,通常设置为 0,表使用默认属性。
它可以是一下值的“按位或”组合: 
SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃) 
SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。 
SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD信号,这时子进程如果退出也不会成为僵尸进程。 
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。 
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。 
SA_SIGINFO:使用 sasigaction成员而不是 sahandler 作为信号处理函数。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
void my_func(int signo)
{
    printf("CTRL+C被按下了\n");
    //_exit(-1);
}

int main(int argc, char const *argv[])
{
    struct sigaction act;
    // act存放回调函数
    act.sa_handler = my_func;

    // act给sa_mask赋值
    //清空阻塞集
    // sigemptyset(&act.sa_mask);
    //将所有信号添加到阻塞集中
    sigfillset(&act.sa_mask);

    //信号的处理方式
    // act.sa_flags = 0;
    act.sa_flags |= SA_RESETHAND;

    //注册CTRL+C信号-->SIGINT
    sigaction(SIGINT, &act, NULL);

    while (1)
        ;
    return 0;
}

6.信号集

在 PCB 中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决信号集”。 这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对其进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB中的这两个信号集进行修改。

信号集操作函数:

#include <signal.h>
int sigemptyset(sigset_t *set); //将 set集合置空
int sigfillset(sigset_t *set); //将所有信号加入 set集合
int sigaddset(sigset_t *set, int signo); //将 signo信号加入到 set集合
int sigdelset(sigset_t *set, int signo); //从 set集合中移除 signo信号
int sigismember(const sigset_t *set, int signo); //判断信号是否存在

7.信号阻塞集

信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。 所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。

sigprocmask函数

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/*功能:检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
参数:
how : 信号阻塞集合的修改方法,有 3 种情况:
SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是 set 和旧信
号掩码的并集。 相当于 mask = mask|set。
SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 s
et 中的信号。相当于 mask = mask & ~ set。
SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内
容清空,然后按照 set 中的信号重新设置信号阻塞集。相当于 mask = set。
set : 要操作的信号集地址。
若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到
oldset 中。
oldset : 保存原先信号阻塞集地址
返回值:
成功:0,
失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。*/

代码案例:

#define _POSIX_SOURCE

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

int main(int argc, char const *argv[])
{
    sigset_t set;
    int ret = 0;
    ret = sigemptyset(&set);

    if (ret == 0)
    {
        printf("set集合清空成功\n");
    }
    else
    {
        printf("set集合清空失败\n");
    }
    sigaddset(&set, SIGINT);
    printf("添加成功\n");
    ret = sigprocmask(SIG_BLOCK, &set, NULL);

    if (ret == 0)
    {
        printf("集合添加到阻塞集成功成功\n");
    }
    else
    {
        printf("集合添加到阻塞集成功失败\n");
    }
    printf("5s后清空阻塞集合\n");
    sleep(5);
    sigprocmask(SIG_UNBLOCK,&set,NULL);

    return 0;
}

8.读取信号未决集

sigpending函数

#include <signal.h>
int sigpending(sigset_t *set);
/*功能:读取当前进程的未决信号集
参数:
set:未决信号集
返回值:
成功:0
失败:-1*/

代码案例:

#define _POSIX_SOURCE

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

void printsigset(const sigset_t *set)
{
    int i = 0;
    for ( i = 0; i < 32; i++)
    {
        if (sigismember(set,i) == 1)
        {
            putchar('1');
        }
        else
        {
            putchar('0');
        }
        
    }
    

}

int main(int argc, char const *argv[])
{
    sigset_t s,p;
    sigemptyset(&s);
    sigaddset(&s,SIGINT);

   
    sigprocmask(SIG_BLOCK, &s, NULL);

    while (1)
    {
        sigpending(&p);
        printsigset(&p);
       // sleep(1);
    }
    
    return 0;
}

按下ctrl+c

未决信号集里有未决信号,因为ctrl+c被加入了阻塞集合

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值