LINUX中的消息传递函数分析

                                       LINUX中的消息传递函数分析

sigset_t

  号集及信号集操作函数:信号集被定义为一种数据类型:

  typedef struct {

  unsigned long sig[_NSIG_WORDS];

  } sigset_t

未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略

信号集用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:

  int sigemptyset(sigset_t *set);

  int sigfillset(sigset_t *set);

  int sigaddset(sigset_t *set, int signum)

  int sigdelset(sigset_t *set, int signum);

  int sigismember(const sigset_t *set, int signum);

  

头文件

  #include <signal.h>

  sigemptyset(sigset_t *set)初始化由set指定的信号集,信号集里面的所有信号被清空;

  sigfillset(sigset_t *set)调用该函数后,set指向的信号集中将包含linux支持的64种信号;

  sigaddset(sigset_t *set, int signum)在set指向的信号集中加入signum信号;

  sigdelset(sigset_t *set, int signum)在set指向的信号集中删除signum信号;

  sigismember(const sigset_t *set, int signum)判定信号signum是否在set指向的信号集中。

信号产生方式

  1. 通过终端按键产生。 如ctrl+c 终止进程
  2. 通过系统函数向进程发送信号。如kill()函数,给指定进程发送信号。
  3. 有软件条件产生信号。如alarm()函数,设定一个闹钟信号。
  4. 硬件异常。如内存越界,除0异常。

利用kill()函数实现自己的kill 命令

// 发送信号给进程
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid,  int sig);

先写一个死循环程序test并在后台跑起来

//test.c
#include <stdio.h>
int main()
{
    while(1);
    return 0;
}

这是利用kill 函数实现的mykill:

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

int main( int argc, char** argv)
{
    if(argc != 1)
    {
        printf("parameter error.\n");
        exit(1);
    }

    // 获取pid
    pid_t pid = atoi(argv[1]);

    kill(pid, SIGKILL);

    return 0;
}

testrun

然后执行jobs ,可以看到后台作业test已经存在。

jobs

此时执行mykill 并 输入test程序的pid

jobs

第一次执行jobs发现test状态已经从Running 变为 Killed,再次执行就会看到已经被干掉。

认识alarm() 函数

#include <unistd.h>

unsigned int alarm( unsigned int seconds);

alarm函数可以设定一个闹钟,告诉内核在seconds秒之后给当前进程发送SIGALRM信号,该信号的默认处理动作是终止当前进程。 函数返回值是0,或者是上次闹钟剩余时间。

比如先设置闹钟10s, 然后调用alarm(0) 表示取消闹钟, 如果返回0, 说明闹钟是在10后响的,如果大于0,则说明该闹钟提前响了。

下面是一个使用alarm函数的小栗子, 测试一秒对可以执行多少次++,并在每次++后打印值:

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


int main()
{
    int count = 0;

    alarm(1);
    while(1)
    {
        count++;
        printf("count is  %d .\n", count);
    }
    return 0;
}

信号的处理方式

一个进程在收到一个信号后,有三种方式处理方式:
1. 忽略此信号
2. 执行默认动作
3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态,这种方式也叫捕捉信号。

一个捕捉信号的小例子:

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


void sigact(int  num)
{
    printf("\n %d号 信号 被我吃了. \n", num);
}


int main()
{
    printf("catch start ... \n");
    signal(SIGINT, sigact); // 捕捉 SIGINT 信号,提供自定义动作
    while(1)
    {
        sleep(1);
        printf("你杀不掉我 hhh \n");
    }
    return 0;
}

catch_2

在代码中,我们捕捉了SIGINT 信号,也就是2号信号(kill -l 可以看到全部信号列表), 操作系统收到ctrl + c ,发送该信号给当前前台作业。发现执行后它每个一秒打印一条“你杀不掉我 hhh”, 当我们按ctrl+c 也无济于事,因为我们为该进程提供了2号信号的自定义函数“信号被我吃掉了”。最后我们只能以ctrl + \干掉它。

信号在内存中的表示

以上讨论了信号产生的各种原因,而实际执行信号的动作称为递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以设置阻塞(Block)某个信号。

如果一个进程阻塞了某个信号,那么在它收到被阻塞的信号时,该信号会处于未决状态。直到对该信号解除阻塞,才会执行抵达的动作。

需要注意的是,信号阻塞和信号忽略是不同的。信号忽略是在该信号被递达后执行的动作,而阻塞说明该该信号在解除阻塞之前不可能递达。

我们知道在系统中运行的每一个进程都有一个 PCB, 而一个进程对应的信号信息也会被操作系统记录在该进程的 PCB 上。在task_struct 结构体会有对应的字段来记录进程当前是否有待处理的信号, 还有记录当前要阻塞的信号,以及信号对应的处理函数。下面用一张图来说明一下关系:

SIGNAL表示

我们可以这样理解:以看做在PCB中有三张表格,分别是block表,pending表,handler表。

block表中1 表示该信号被阻塞,当有信号产生时不会被抵达,会处于未决状态。
而pending表记录未被处理的信号。handler 对应每个信号的处理方式,有默认和忽略,以及一个函数指针,指向我们提供的处理函数。

一大波信号集处理函数袭来

通过上面的表格可以发现,阻塞和未决状态,每个信号只需要对应一个bit位即可解决1表示有效,0表示无效,所以系统为我们提供了信号量集 sigset_t 来保存阻塞和未决的状态。

#include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

以上两个函数用来将信号机全部置0或全部置1,在使用之前,务必调用对应初始化函数,让信号机处于确定状态。


int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

上面两个函数用来给,指定信号集,添加或删除signum信号。


int sigismember(sigset_t *set, int );

用来判断信号集中是否有该信号,有则返回1,无返回0,执行失败返回-1。

sigprocmask函数

用来读取或者更改进程的信号屏蔽集。

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

如果 set 为空 oldset 不为空, 则会将进程原来的信号屏蔽集传出;
如果 set 不为空,oldset 为空, 则会根据 how 参数的指示修改进程的信号屏蔽集;
如果两个指针都不为空, 则先备份原来的信号屏蔽字到 oldset, 然后根据 how参数修改。

how参数有如下函数

含义
SIG_BLOCK 将当前进程屏蔽字mask 增加我们希望通过参数set的屏蔽信号,相当于mask与set执行按位或
SIG_UNBLOCK 将当前进程屏蔽字mask 删除我们希望通过参数set解除屏蔽的信号
SIG_SETMASK 设置当前信号屏蔽字为set,相当于 mask = set

sigpending

用来读取当前进程的未决信号集。

#include <signal.h>
int sigpending(sigset_t *set);

下面运用上面介绍的信号集函数写一个小实验。

程序运行时,每秒打印一此未决状态信号集,初始全为0,当输出ctrl-c时, 由于我们阻塞了SIGINT信号, 会使该信号处于未决状态。

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


// 打印信号集
void printsigset(const sigset_t *set)
{
    int i = 0;
    for(; i<32; ++i)
    {
        if(sigismember(set, i) == 1)
            printf("1");
        else
            printf("0");

    }
    printf("\n");
}

int main()
{

    sigset_t s;
    sigemptyset(&s); // 初始化
    sigaddset(&s, SIGINT);
    sigprocmask(SIG_BLOCK, &s, NULL );

    while(1)
    {
        sigpending(&s);
        printsigset(&s);
        sleep(1);
    }


    return 0;
}

printpendingset

此篇文章是对大佬们的讲解整理的,感觉还不错!

猜你喜欢

转载自blog.csdn.net/QQ_Peng123/article/details/82384122