2017-07-31 15:45:15 weixin_33398032 阅读数 231
  • 玩转Linux:常用命令实例指南

    本课程以简洁易懂的语言手把手教你系统掌握日常所需的Linux知识,每个知识点都会配合案例实战让你融汇贯通 。课程通俗易懂,简洁流畅,适合0基础以及对Linux掌握不熟练的人学习; 注意: 1、本课程本月原价99元,截止2月22日前仅需29元!购课就送5门价值300元的编程课! 2、课程内容不仅是大纲上这些,2月底前老师会继续增加10余节课程,届时会恢复原件!现在购买最划算! 3、购课后登陆csdn学院官网,在课程目录页面即可下载课件。 学完即可轻松应对工作中 85% 以上的 Linux 使用场景 【限时福利】 1)购课后按提示添加小助手,进答疑群,还可获得价值300元的编程大礼包! 2)本课程【现在享受秒杀价29元】 3)本月购买此套餐加入老师答疑交流群,可参加老师的免费分享活动,学习最新技术项目经验。 注意: 1)现在购买至少享受70元优惠; 2)购课后添加微信itxy06,发送订单截图领取300元编程礼包。 --------------------------------------------------------------- 这门课程,绝对不会让你觉得亏! 29元=掌握Linux必修知识+社群答疑+讲师社群分享会+300元编程礼包。   人工智能、物联网、大数据时代,Linux正有着一统天下的趋势,几乎每个程序员岗位,都要求掌握Linux。本课程零基础也能轻松入门。   在这门课中,我们保证你能收获到这些 1)快速掌握 Linux 常用命令及配置 2)Linux核心知识点 3) 文件、进程、磁盘、用户管理,以及网络工具命令、文件传输等 4)Vi/Vim编辑器用法  

    5367 人正在学习 去看看 严宇

1、自定义SIGNAL函数。可设置在执行信号处理函数过程中,是否能被其它信号或者与当前信号相同信号中断。

#define SIGNAL(s, handler)do { \
sa.sa_handler = handler; \
if (sigaction(s, &sa, NULL) < 0) \
printf("Couldn't establish signal handler (%d)", s); \
} while (0)

sa.sa_mask = g_sync_signals;    //捕捉到某信号时,执行信号处理函数过程中,被屏蔽(阻塞)的信号集合设置为g_sync_signals

sa.sa_flags = 0;//默认不设置SA_NODEFER标志。即捕捉到某一信号时,执行信号处理函数过程中,会暂时阻塞该信号,信号处理结束后,只会触发一次该信号。

其它标志及用法请参考unix高级环境编程。


2、使用sigprocmask函数进行信号屏蔽

#include <signal.h>

int sigprocmask(int how, const sigset_t *restrict_set, sigset_t *restrict_oset)

how取值如下:

SIG_BLOCK//“或”操作。新的信号屏蔽字是,当前信号屏蔽字与set指向的信号屏蔽字的并集。

SIG_UNBLOCK//解除信号屏蔽。

SIG_SETMASK//赋值操作。新的信号屏蔽字是,set指向的信号屏蔽字。

关于oset

不为NULL时,可获取设置新的信号屏蔽字前的信号屏蔽字

举例:
sigprocmask(SIG_BLOCK, &g_sync_signals, NULL);  //屏蔽信号
sigprocmask(SIG_UNBLOCK, &g_sync_signals, NULL); //解除信号屏蔽

3、信号屏蔽过程中,向进程发送多个不同的信号多次。那么,在解除屏蔽后,每个信号只会触发一次。(每个信号只会被递送一次給当前进程)


4、SIGCHLD信号处理函数中,千万不能调用system()函数。

system()函数中,会先屏蔽SIGCHLD信号。这是为了保证system()中启动的子进程退出时,system()函数中的waitpid能够回收子进程,从而不会触发SIGCHLD信号处理函数。然而,当system()函数结束前,解除信号屏蔽后,仍会触发一次SIGCHLD信号,并执行信号处理函数。这就会导致程序一直触发SIGCHLD信号,并无限循环,根本停不下来。


2017-09-24 16:26:20 weixin_38190650 阅读数 790
  • 玩转Linux:常用命令实例指南

    本课程以简洁易懂的语言手把手教你系统掌握日常所需的Linux知识,每个知识点都会配合案例实战让你融汇贯通 。课程通俗易懂,简洁流畅,适合0基础以及对Linux掌握不熟练的人学习; 注意: 1、本课程本月原价99元,截止2月22日前仅需29元!购课就送5门价值300元的编程课! 2、课程内容不仅是大纲上这些,2月底前老师会继续增加10余节课程,届时会恢复原件!现在购买最划算! 3、购课后登陆csdn学院官网,在课程目录页面即可下载课件。 学完即可轻松应对工作中 85% 以上的 Linux 使用场景 【限时福利】 1)购课后按提示添加小助手,进答疑群,还可获得价值300元的编程大礼包! 2)本课程【现在享受秒杀价29元】 3)本月购买此套餐加入老师答疑交流群,可参加老师的免费分享活动,学习最新技术项目经验。 注意: 1)现在购买至少享受70元优惠; 2)购课后添加微信itxy06,发送订单截图领取300元编程礼包。 --------------------------------------------------------------- 这门课程,绝对不会让你觉得亏! 29元=掌握Linux必修知识+社群答疑+讲师社群分享会+300元编程礼包。   人工智能、物联网、大数据时代,Linux正有着一统天下的趋势,几乎每个程序员岗位,都要求掌握Linux。本课程零基础也能轻松入门。   在这门课中,我们保证你能收获到这些 1)快速掌握 Linux 常用命令及配置 2)Linux核心知识点 3) 文件、进程、磁盘、用户管理,以及网络工具命令、文件传输等 4)Vi/Vim编辑器用法  

    5367 人正在学习 去看看 严宇

信号传递过程

  • 信号源产生信号,由内核向进程发送信号
  • 进程选择是否阻塞进程,若阻塞,则信号进入阻塞信号列表,只有当解除阻塞后,进程才接收该信号,若一直不接触,内核则将该信号从阻塞列表中移除并丢弃;若不阻塞,则进程接收信号
  • 进程接收信号后,进程可屏蔽该信号,或者执行用户编写的处理函数,或者执行默认动作

    以上便是linux进程对信号的处理过程,如果你足够细心,你会提出疑问:我如何才能让进程对一个信号进行处理呢?这也就是我们这篇博客要解决的问题

一些名词

  • 信号集
    信号集顾名思义,就是信号的集合。在linux中,它的类型是sigset_t,大小是64bits
    此时你应该又有疑问了:为何是64bits?原因很简单,因为目前linux流行版本一共有64个信号(不同版本信号格式不同),我们一个bit来表示一种信号,一共只需要64bits就行啦!!!
    那么信号集的作用到底是什么呢?信号集一共有64bits,自然,第一位就是1号信号(SIGHUP),第二位就是2号信号(SIGINT)….以此类推(我们假设位号是从1开始,而不是从0开始)。那么,假如1号位如果是1,则表示1号信号已经注册到该信号集中;如果是0,就是为注册到该信号集中咯!为什么要把信号注册到信号集中呢?当然是为了批量管理信号!!!具体的用法,看到下面你就知道啦

  • 抵达(delivery) && 未决(pending)
    抵达:执行信号的处理动作叫抵达。也就是说信号被进程接收啦!抵达包括:忽略执行默认动作执行处理函数
    未决:信号从产生到抵达直接的状态。说白了未决就是未抵达。自然,一个信号被阻塞啦,那么该信号就是处于未决状态。

  • 信号屏蔽状态字(block) && 信号未决状态字(pending)
    在进程控制块(PCB)中的结构体中,有三个比较重要的变量,分别是:信号屏蔽状态字信号未决状态字是否忽略标志
    信号屏蔽状态字(block):64bits,每一位代表该进程对对应号码的信号是否屏蔽:1是屏蔽,0是不屏蔽
    信号未决状态字(pending):64bits,每一位代表该进程对对应号码的信号的状态:1是未决,0是不抵达
    是否忽略标志:这里我们不讨论

需要注意的是:这些变量之间也存在一些关系,比如:进程将信号屏蔽字的2号为置为1,也就是说屏蔽SIGINT信号,那么但你向进程发送该信号时(ctrl+c),该信号必然处于未决状态。那么,信号未决状态字的2号位自然也就是1啦。

信号集操作函数

POSIX.1 定义了一个数据类型sigset_t,用于表示信号集。另外,头文件 signal.h 提供了下列五个处理信号集的函数

  • sigemptyset 初始化set所指向的信号集,清除里面所有已经注册的信号,即将所有位置0
    int sigemptyset(sigset_t *set);
    返回值:若成功则返回0,若出错则返回-1

  • sigfillset 初始化由 set 指向的信号集,使其包含所有信号。即将所有位置1
    int sigfillset(sigset_t *set);
    返回值:若成功则返回0,若出错则返回-1

  • sigaddset 将一个信号 signo 添加到现有信号集 set 中。即将该信号对应的位置1
    int sigaddset(sigset_t *set, int signo);
    返回值:若成功则返回0,若出错则返回-1

  • sigdelset 将一个信号 signo 从信号集 set 中删除。即将该信号对应的位置0
    int sigdelset(sigset_t *set, int signo);
    返回值:若成功则返回0,若出错则返回-1

  • sigismember 判断指定信号 signo 是否在信号集 set 中。
    int sigismember(const sigset_t *set, int signo);
    返回值:若真则返回1,若假则返回0,若出错则返回-1

这些函数的具体运用会在下面的代码中

sigprocmask && 设置进程的信号屏蔽字

  • sigprocmask 函数可以检测或者设置进程的信号屏蔽字。
    #include <signal.h>
    int sigprocmask(int how, const sigset_t * set, sigset_t * oset);
    返回值:若成功则返回0,若出错则返回-1

  • 参数说明
    set:如果set是非空指针,则更改进程的信号屏蔽字
    oset:如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出
    how:参数how指示如何更改
    注意如果set为空,那how参数显然也没有意义啦,信号集都没有,我哪知道要改哪个信号的屏蔽状态字!!!

  • how 参数可选值

how 说明
SIG_BLOCK 该进程新的信号屏蔽字是其当前信号屏蔽字和 set 指向信号集的并集。set 包含了我们希望阻塞的信号。
SIG_UNBLOCK 该进程的信号屏蔽字是当前信号屏蔽字和 set 所指向信号集补给的交集。set 包含了我们希望解除阻塞的信号。
SIG_SETMASK 设置当前信号屏蔽字设置为 set 所指向的信号集。

上面的东西是不是不是很懂,那么我们通俗点讲

how 说明
SIG_BLOCK 把set信号集里面的信号全部设置为阻塞
SIG_UNBLOCK 把set信号集里面的信号全部解除阻塞
SIG_SETMASK 把set信号集里面的信号全部设置为阻塞或者解除阻塞(当前信号屏蔽字可能是阻塞,,也可能是接触阻塞)
  • 下面我们来看一个具体的例子
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

void handler()
{
    printf("SIG_INT respond\n");
    return;
}

int main()
{
    char tmp = 'a';
    sigset_t bset; //用来设置阻塞的信号集

    sigemptyset(&bset); //清空信号集
    sigaddset(&bset, SIG_INT); //将SIG_INT信号添加到信号集中

    if(signal(SIG_INT, handler) == SiG_ERR)//注册安装处理函数
        perror("signal err:");

    sigprocmask(SIG_BLOCK, &bset, NULL); //阻塞SIG_INT信号

    while(tmp != 'q' )
    {
        tmp = getchar();
    }
    sigprocmask(SIG_UNBLOCK, &bset, NULL);//解锁阻塞

    pause();
    return 0;
}


编译运行此函数,按一下步骤输入:
程序运行到getchar(), 输入ctrl+c,此时并未看见执行信号处理函数
输入'q',退出循环,再按ctrl+c,看见控制端打印SIG_INT respond

sigpending 获取进程未决的信号集
  • sigpending 获取当前进程所有未决的信号。通过其 set 参数返回未决的信号集。
    #include <signal.h>
    int sigpending(sigset_t *set);
    返回值:若成功则返回0,若出错则返回-1

通俗地将,就是:一旦调用sigpending函数,那么set信号集中,处于未决状态的信号对应的位,被置为1

  • 看个例子
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

void printsigset(sigset_t *set) 
{
    int i;
    for (i=1; i<NSIG; ++i)
    {
        if (sigismember(set, i))//置1的位,说明对应信号在信号集 set中。返回真,则打印1。如01000000000000....说明2号信号是处于未决状态
            putchar('1');
        else
            putchar('0');
    }
    printf("\n");
}


int main()
{
    char tmp = 'a';
    sigset_t bset; //用来设置阻塞的信号集
    sigset_t pset; //用来打印未决信号集

    sigemptyset(&bset); //清空信号集
    sigaddset(&bset, SIG_INT); //将SIG_INT信号添加到信号集中

    sigprocmask(SIG_BLOCK, &bset, NULL); //阻塞SIG_INT信号

    for(;;;)
    {
        //获取未决 字信息
        sigpending(&pset);//若一信号是未决状态,则将set对应位置1

        //打印信号未决 sigset_t字
        printsigset(&pset);
        sleep(1);
    }
    pause();
    return 0;
}


程序开始执行:打印000000000000......
输入ctrl+c,因为SIG_INT被阻塞,所以该信号处于未决状态
故输出01000000000000000......

需要注意的是:如果在信号处理函数中对某个信号进行解除阻塞时,则只是将pending位清0,让此信号递达一次(同个实时信号产生多次进行排队都会抵达),但不会将block位清0,即再次产生此信号时还是会被阻塞,处于未决状态。

2017-03-16 23:47:04 qq_29924041 阅读数 1681
  • 玩转Linux:常用命令实例指南

    本课程以简洁易懂的语言手把手教你系统掌握日常所需的Linux知识,每个知识点都会配合案例实战让你融汇贯通 。课程通俗易懂,简洁流畅,适合0基础以及对Linux掌握不熟练的人学习; 注意: 1、本课程本月原价99元,截止2月22日前仅需29元!购课就送5门价值300元的编程课! 2、课程内容不仅是大纲上这些,2月底前老师会继续增加10余节课程,届时会恢复原件!现在购买最划算! 3、购课后登陆csdn学院官网,在课程目录页面即可下载课件。 学完即可轻松应对工作中 85% 以上的 Linux 使用场景 【限时福利】 1)购课后按提示添加小助手,进答疑群,还可获得价值300元的编程大礼包! 2)本课程【现在享受秒杀价29元】 3)本月购买此套餐加入老师答疑交流群,可参加老师的免费分享活动,学习最新技术项目经验。 注意: 1)现在购买至少享受70元优惠; 2)购课后添加微信itxy06,发送订单截图领取300元编程礼包。 --------------------------------------------------------------- 这门课程,绝对不会让你觉得亏! 29元=掌握Linux必修知识+社群答疑+讲师社群分享会+300元编程礼包。   人工智能、物联网、大数据时代,Linux正有着一统天下的趋势,几乎每个程序员岗位,都要求掌握Linux。本课程零基础也能轻松入门。   在这门课中,我们保证你能收获到这些 1)快速掌握 Linux 常用命令及配置 2)Linux核心知识点 3) 文件、进程、磁盘、用户管理,以及网络工具命令、文件传输等 4)Vi/Vim编辑器用法  

    5367 人正在学习 去看看 严宇

linux下进程的信号量

信号:

  1. 信号的基本概念
  2. 信号与异常处理
  3. 信号的处理方法
  4. 信号的可靠性
  5. 函数可重入性
  6. 信号集
  7. 信号屏蔽

首先来介绍一下什么是信号,信号的基本概念

1:信号(signal)机制是Linux系统中最为古老的进程之间的通信机制解决进程在正常运行过程中被中断的问题,导致进程的处理流程会发生变化
2:信号是软件中断
3:信号是异步事件

a:不可预见
b:信号有自己的名称和编号
c:信号和异常处理机制

4:信号发生的来源

a:硬件来源,如按键的事件,或者硬件故障,是由驱动程重点内容序产生
b:软件来源:最常用发送信号的系统函数 kill(),raise(),alarm()和setitimer()等函数
软件来源还包括一些非法运算等操作,软件设置条件如(gdb调试),信号由内核产生;

5:查看linux系统内置的信号,在前面博客中其实也提到相关信号:

linux信号类型

使用kill -l来查看信号列表
信号是没有优先级之分的;
1-31称为非实时信号,发送的信号可能会丢失,不支持信号排队,
32-64成为实时信号,支持信号排队,发送的多个实时信号都会被接受

前面31个信号类型在linux中的/usr/include/bits/signum.h文件中会有详细定义

6: 信号的历史变革

    信号出现在早期的Unix中
    早期信号模型是不可靠的
    BSD和System V分别对早期信号进行扩展,但是相互不兼容
    POSIX统一了上述两种模型,提供了可靠信号模型

信号的处理方式:

进程可以通过三种方式来响应和处理一个信号
    1:忽略信号
        SIGKILL和SIGSTOP永远不能忽略
        忽略硬件异常
        进程启动时SIGUSR1和SIGUSR2两个信号会被忽略

    2:执行默认操作
        每个信号有默认动作,大部分信号默认动作都是终止进程 

    3:捕获信号
        告诉内核出现信号时调用自己的处理函数
        SIGKILL和SIGSTOP不能被捕获

信号的处理方法

信号注册(接收)函数
1. signal函数

signal函数

#include<signal.h>
void (*signal(int signo,void(*func)(int))) (int);

返回:若成功则返回先前的信号处理函数指针(func),出错返回SIG_ERR
功能:向内核登记信号处理函数
参数:
    signo
        要登记的信号值,一般用信号的宏来
    func
        信号处理函数指针
        SIG_IGN
            忽略信号
        SIG_DFL
            采用系统默认的方式处理信号,执行默认操作            
使用man signal可以查看这个函数的具体的函数定义

非常简单的实例代码

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


void signal_handler(int signo){
    printf("signo:%d\n",signo);
}
int main(int agrc,char * argv[]){
    //ctrl+c    
    if(signal(SIGINT,signal_handler) == SIG_ERR){
        perror("sigint error");
    }

    if(signal(SIGSEGV,signal_handler) == SIG_ERR){
        perror("sigsegv error");
    }

    //SIGUSR1,SIGUSR2在不捕获的状态下,默认是忽略的
    if(signal(SIGUSR2,signal_handler) == SIG_ERR){
        perror("siguser1 error");
    }

    if(signal(SIGUSR1,signal_handler) == SIG_ERR){
        perror("siguser2 error");
    }
    //sigkill和sigstop是不允许被忽略和捕获的,在注册的时候就已经会报错
    if(signal(SIGKILL,SIG_DFL) == SIG_ERR){
        perror("sigkill error");
    }

    if(signal(SIGSTOP,SIG_DFL) == SIG_ERR){
        perror("signal stop error");
    }
    //ctrl+z    
    if(signal(SIGTSTP,signal_handler) == SIG_ERR){
        perror("signal tstp error");
    }
    int i = 0;
    while(++i < 50){
        printf("pid:%d,count:%d\n",getpid(),i);
        sleep(1);
    }

    return 0;
}

怎么使用信号来处理僵尸进程

 SIGCHILD信号
    子进程状态发生改变(子进程结束)产生该信号,父进程需要使用wait调用来等待子进程结束并回收它。
    避免僵尸进程

子进程结束之后自动产生的该信号;
当父进程捕获到SIGCHILD信号之后,一定要去调用wait(0)函数,否则子进程会成为僵尸进程
优点:只有当子进程终止之后,父进程才调用wait函数,而如果父进程直接去调用wait函数的话,那这个时候可能就会导致父亲进程会直接阻塞住。这样就会导致父进程执行的效率极低

父亲进程通过注册去监听回收僵尸进程部分的代码

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

void singal_handler(int signo){
    printf("%d occred %d\n",getpid(),signo);
    //释放子进程(僵尸进程)
    wait(NULL);
}

void outNum(int num){
    int i = 0;
    for(i = 0;i<num;i++){
        printf("%d count %d\n",getpid(),i);
        sleep(2);
    }
}
int main(int argc,char * argv[]){

    pid_t pid = fork();

    if(pid < 0){
        perror("fork error\n");
        exit(1);
    }else if(pid > 0){
        //注册等级子进程deaded的时候的信号
        if(signal(SIGCHLD,singal_handler)==SIG_ERR){
            perror("signal child error");
        }
        outNum(20);
    }else {
        outNum(10);
    }
    return 0;
}

信号注册(接收)函数
信号发送:

除了内核和超级用户,并不是每个进程都可以向其他进程发送信号
一般的进程只能向具有相同uid和gid的进程发送信号或相同进程组中的其他进程发送信号
常用的发送信号的函数有
1:kill(),
2:raise(),
3:alarm(),
4:setitimer(),
5:abort()等

1:kill函数和raise函数

#include<signal.h>
int kill(pid_t pid,int signo);
返回:成功返回0,出错返回-1
功能,向指定的进程发送某一个信号
int raise(int signo);
返回:成功返回0,出错返回-1
功能:向进程本身发送一个信号,相当于kill(getpid(),signo);
参数:
    pid 接受信号的pid
    signo 要发送的信号值

kill函数将信号发送给进程或者进程组
    0为空信号,常常用来检测特定的进程是否存在


参数pid取值:
    pid > 0 将信号发给进程ID为pid的进程
    pid==0将信号发给与发送进程同一进程组的所有进程
    pid<0将信号发送给进程组ID等于pid的绝对值
    pid==-1将信号发送给发送进程有权限向他们发送信号的系统上的所有进程

2:alarm函数:

#include<unistd.h>
unsigned int alarm(unsigned int seconds);
useconds_t ualarm(useconds_t usecs, useconds_t interval);


返回:0或以前设置的定时器时间余留的秒数
alarm函数可设置定时器 ,当定时器超时,产生一个SIGALRM信号
该信号由内核产生,在制定的seconds秒之后,给进程本身发送 一个SIGALRM信号
参数为0,取消以前设置的定时器
ualarm是微秒为单位的

上面信号发送的两个函数的调用其实也相对来说比较简单。在这里就不去进行演示了。
总结一下进程的信号量问题:
其实linux进程的信号量问题给我的感觉就好像是android里面的广播机制一样,如果你想要去接受到广播的话,那么就必须提前去注册一个广播的监听,这样才会有接受广播的资格,而进程信号其实也是,要想在进程中去监听信号的话,也必须要先通过singal去注册,而发广播其实就是linux进程信号的发送方式,可以通过kill,raise,等函数调用。去向指定进程(注册过信号的)发送消息。而指定进程根据消息类型类选择执行的方式。
谢谢大家的观看。写的不好的地方,希望能够给予指出。

一起交流学习的qq群号为324652573

欢迎持续访问博客

2018-09-30 21:36:26 ruo_bing 阅读数 60
  • 玩转Linux:常用命令实例指南

    本课程以简洁易懂的语言手把手教你系统掌握日常所需的Linux知识,每个知识点都会配合案例实战让你融汇贯通 。课程通俗易懂,简洁流畅,适合0基础以及对Linux掌握不熟练的人学习; 注意: 1、本课程本月原价99元,截止2月22日前仅需29元!购课就送5门价值300元的编程课! 2、课程内容不仅是大纲上这些,2月底前老师会继续增加10余节课程,届时会恢复原件!现在购买最划算! 3、购课后登陆csdn学院官网,在课程目录页面即可下载课件。 学完即可轻松应对工作中 85% 以上的 Linux 使用场景 【限时福利】 1)购课后按提示添加小助手,进答疑群,还可获得价值300元的编程大礼包! 2)本课程【现在享受秒杀价29元】 3)本月购买此套餐加入老师答疑交流群,可参加老师的免费分享活动,学习最新技术项目经验。 注意: 1)现在购买至少享受70元优惠; 2)购课后添加微信itxy06,发送订单截图领取300元编程礼包。 --------------------------------------------------------------- 这门课程,绝对不会让你觉得亏! 29元=掌握Linux必修知识+社群答疑+讲师社群分享会+300元编程礼包。   人工智能、物联网、大数据时代,Linux正有着一统天下的趋势,几乎每个程序员岗位,都要求掌握Linux。本课程零基础也能轻松入门。   在这门课中,我们保证你能收获到这些 1)快速掌握 Linux 常用命令及配置 2)Linux核心知识点 3) 文件、进程、磁盘、用户管理,以及网络工具命令、文件传输等 4)Vi/Vim编辑器用法  

    5367 人正在学习 去看看 严宇

信号

一 、 概念和功能:

信号实际上是一个软中断,用于通知进程发生了某些事,该如何处理。
实际上也归为一类进程间通信方式, 信号的生命周期:信号的产生-信号的注册-信号的阻塞(/屏蔽)-信号的注销-信号的处理

二 、 查看信号:

kill -l ,查看linux下全部信号,可看出信号是由编号和宏组成;
这里写图片描述

kill并不是杀死一个进程,而是为了给某一个指定的进程发送信号。

linux下有62 个信号,分为两类:1-31是非可靠信号(并非实信号),1-31是继承unix而来的,每个信号都对应一个指定事件,非可靠代表这个信号可能会丢失,如果有相同的信号已经注册到这个进程没有被处理,那么接下来的相同信号就会丢掉。
34-64,为可靠信号(实信号)

三 、信号产生方式:

1.硬件中断 (键盘按键中断,ctrl c);
2.硬件异常; (段错误,内存访问错误,core dump,默认发生的信号是11 号SIGSEGV)
3.命令产生:kill 命令 (kill -n pid),默认情况下发送的是信号15SIGTERM
4.软件条件产生:kill函数,raise函数,alarm函数,sigqueue函数

给进程发送信号的接口函数


int kill(pid_t pid,int sig)         //向指定进程发送信号
参数:进程id,指定发送给哪个进程	;信号的编号,指定发送哪个信号   (信号是由编号和宏组成  2 SIGINIT)

int raise(int sig)            //给当前进程发送信号

int sigqueue(pid_t pid,int sig,union sigval value) //给指定进程发送信号,并且可以传一个参数过去,value是要携带的数据,其类型是union sigval

Unsigned int  alarm(unsigned  int seconds)//在指定second秒后给进程发送信号

这里写图片描述

四 、 core dump

   当一个程序要异常终止时,可以选择吧进程用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做core  Dump。
   程序异常的时候,会记录一个核心转储文件,在这个文件中记录程序运行数据 。当一个程序奔溃时,这个错误可能只是偶尔发生,这种错误将很难定位,因为不确定到底什么时候才会崩溃,因此这个转储文件(core 文件)就非常重要,它可以帮助我们使用gdb调试 查看,定位错误。  
    ulimit -c size ,可以设置core dump的大小,例如 ulimit -c 1024
    gdb进出程序,运行执行程序(run ),bt,用来查看调用栈。在gdb中使用命令:core-file core.3996()来加载程序的的运行数据,然后就可以定位错误了。 但是程序的核心转储功能是**默认关闭的**,转储文件默认大小为0,因此运行数据中可能有安全性信息,以及文件增多会占用资源 查看文件/改变文件的大小:

信号的注册

将信号记录在进程中,让进程知道有这样一个信号来了。

信号是记录在进程的PCB中 。
信号集合:在PCB中sigset_t 结构体
进程记录一个信号的时候,通过这个结构体的位图来记录

注册:修改进程中PCB中信号pending的位图,添加一个信号的sigqueue结构体节点(pending位图0变1)
注销:修改进程中PCB中信号pending的位图,删除指定信号的sigqueue的结构体节点(pending位图1变0)

这里写图片描述

信号的阻塞与屏蔽

信号阻塞:因为进程PCB 中还有一个结构,这个结构就是block阻塞集合,

在pcb中有 一个pending结构中存储当前接收到的信号,还有一个结构体blocked用于存储 现在有哪些信号要被阻塞。 进程看到了pending集合中都收到了哪些信号,然后就要开始处理这些信号,但在处理这些信号之前,进程先比对一下这些信号有没有存在于blocked集合中,如果存在了(位图为1),意味着现在这个信号不被处理,直到解除阻塞 。

这里写图片描述

信号阻塞接口:
阻塞: int sigprocmask(int how,sigset_t *set,sigset_t *oldset)
how:对集合所做的操作,SIG_BLOCK(对set集合,SIG_UNBLOCK,SIG_SETMASK

实现信号阻塞屏蔽
//先定义集合
//将信号添加到集合中:intsigfillset(sigset_t *set);
int sigpending(sigseet_t set)//将当前pending集合中的信号取出来放到set中 int sigismember(const sigset_tset,int signum) 判断信号是否在集合中

信号的注销

就是从pending中将要处理的信号移除但分情况: 非可靠信号: 注册的时候,是给sigqueue链表添加一个信号结点,并且将pending集合对应位图置1,当这个信号注册的时候,如果位图已经置1,代表信号已经注册过了,那就不做任何操作,不添加新结点 注销:删除链表中的节点,将对应位图置 0 可靠信号: 注册:是给sigqueue链表添加一个信号节点,不管这个可靠信号是否已经注册过,如果没有注册过,就添加新节点,对应位图置1 注销:删除一个节点,然后查看链表中有没有相同的节点,如果有,那么信号对应的位图 110 依然为1,若果没有相同的节点,代表这个信号全部被处理,因此对应位图置0 111 112 可靠信号因为每次信号到来都会添加新节点,所以不会丢失信号。

#信号处理(信号的递达)
当进程收到一个信号时,就意味着现在有一个重要的事情要处理,因此会打断我们当前的操作,然后去处理这件事

  • 什么时候处理信号呢?
    进程从内核态切换到用户态时会去检测是否有信号要处理。

信号的处理方式:

  1. 默认处理:系统定义好的处理方式

  2. 忽略处理方式:忽略和阻塞完全不同,一个被忽略的信号来了以后就直接丢弃

  3. 自定义处理方式:用户自己定义一个的一个处理信号的方式,使操作系统按照定义的这个处理方式来处理这个信号

信号忽略和信号注册的区别,阻塞一个信号后,信号依然会注册在pending集合中,而忽略信号,则信号直接丢弃,不会注册

信号处理方式接口:

sighandler_t signal (int signum, sighandler_t handler)
功能:修改一个信号的处理方式
参数:指定修改哪个信号; 第二个是处理的方式,SIG_IGN(忽略处理),SIG_DFL(默认处理)

int sigaction(int signum,const struct signation *act, struct signation *oldact)
功能:修改信号的处理方式
参数:signum指定修改哪个参数;act是指定的处理动作;oldact 保存信号原来的处理方式
结构体:
struct sigaction {
void (*sa_handler)(int); //自定义处理方式
void (*sa_sigaction)(int, siginfo_t *, void *); //也是自定义处理方式,可以接收信号携带的参数
sigset_t sa_mask; //在处理信号时要阻塞的其他信号(避免其他信号打扰自定义操作)
int sa_flags; //决定用哪个函数作为信号处理接口,默认0则是sa-handler,给定SA_SIGINFO则是sa_sigaction
void (*sa_restorer)(void);
};

信号的捕捉

针对自定义处理方式,进程捕捉到信号然后进行处理的 过程

进程是从内核态切换到用户态时,需要判断是否有信号要处理,然后返回用户态去自定义处理,在返回内核,在返回用户态
如何实现从用户态到内存态的切换?中断,异常,系统调用

可重入函数

可重入函数:某个函数调用的时候,如果中间操作被打断,在其他地方有重复多次调用,不影响运行结果(对其他地方的调用产生影响),这个函数就叫可重入函数
不可重入函数:多次调用影响结果。

可重入函数的特点:操作了一些公共数据

竞态条件:

如果我们的某个操作不是一个原子操作,那么意味着这个操作有可能被打断然后去做其他的事情,这时做其他事情可能产生一些逻辑问题

int  sigsuspend(const sigsey_t *mask)     
  //集合了临时阻塞指定信号,并陷入阻塞等待的一个原子操作


 功能:临时使用mask中的信号替换阻塞结合blocked中的信号,然后进入阻塞等待,唤醒后还原。
 也就是说临时替换阻塞信号,进入休眠,唤醒时 ,在将原来的阻塞信号替换回

自己写的sleep为例,讲解竞态条件,说的是alarm函数和pause函数间如果被打断去干其他事情,可能造成pause永久阻塞

volatile:c语言的关键字,保证内存的可见性。每次处理这个被volatile修饰的变量时,都会从内存中重新加载变量的值都内存器中

因为程序在优化的时候,如果一个变量使用的频率很高的话(比如全局变量)那么,这个变量有可能被优化为只向内存器加载一次,往后直接使用寄存器中保存的值。 而不关心它在内存里的值,因此可能会造成一些逻辑错误

SIGCHILD 信号
自定义处理,循环的原因是对子进程资源随退随回收,且大致不影响逻辑。
信号是会打断进程的阻塞操作,唤醒正在休眠的进程

2019-10-09 17:15:04 u010743406 阅读数 8
  • 玩转Linux:常用命令实例指南

    本课程以简洁易懂的语言手把手教你系统掌握日常所需的Linux知识,每个知识点都会配合案例实战让你融汇贯通 。课程通俗易懂,简洁流畅,适合0基础以及对Linux掌握不熟练的人学习; 注意: 1、本课程本月原价99元,截止2月22日前仅需29元!购课就送5门价值300元的编程课! 2、课程内容不仅是大纲上这些,2月底前老师会继续增加10余节课程,届时会恢复原件!现在购买最划算! 3、购课后登陆csdn学院官网,在课程目录页面即可下载课件。 学完即可轻松应对工作中 85% 以上的 Linux 使用场景 【限时福利】 1)购课后按提示添加小助手,进答疑群,还可获得价值300元的编程大礼包! 2)本课程【现在享受秒杀价29元】 3)本月购买此套餐加入老师答疑交流群,可参加老师的免费分享活动,学习最新技术项目经验。 注意: 1)现在购买至少享受70元优惠; 2)购课后添加微信itxy06,发送订单截图领取300元编程礼包。 --------------------------------------------------------------- 这门课程,绝对不会让你觉得亏! 29元=掌握Linux必修知识+社群答疑+讲师社群分享会+300元编程礼包。   人工智能、物联网、大数据时代,Linux正有着一统天下的趋势,几乎每个程序员岗位,都要求掌握Linux。本课程零基础也能轻松入门。   在这门课中,我们保证你能收获到这些 1)快速掌握 Linux 常用命令及配置 2)Linux核心知识点 3) 文件、进程、磁盘、用户管理,以及网络工具命令、文件传输等 4)Vi/Vim编辑器用法  

    5367 人正在学习 去看看 严宇

随书总结:《linux设备驱动开发》 宋宝华著

主要有:中断屏蔽;原子操作;自旋锁;信号量


1- 中断屏蔽:进入临界区前后关、开中断使能


2- 原子操作:在多进程(线程)的操作系统中不能被其它进程(线程)打断的操作就叫原子操作。原子操作是不可分割的,在执行过程中不会被任何其它任务或事件中断。 linux内核提供了一系列函数来实现内核中的原子操作,内核代码可以安全地调用它们而不被打断。位和整型变量原子操作都依赖底层CPU的原子操作实现,因此所有这些函数都与CPU的架构密切相关。
首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。奔腾6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器不能自动保证其原子性,比如跨总线宽度,跨多个缓存行,跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。
原子性不可能由软件单独保证--必须需要硬件的支持,因此是和架构相关的。在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
总之认为原子操作是由CPU、硬件保证的就好。

3- 自旋锁:在获取资源前后获取取、释放锁,如果获取不到则一直等待,直到获取成功。用于保证临界区不受别的CPU和本CPU内的抢占进程打扰。但是可能受到中断下半部影响,这时可使用加入中断下半部的自旋锁
自旋锁的其他形式:读写锁;顺序锁;rcu读-拷贝-更新锁
注意事项:
a- 自旋锁是一种死等锁,使用与等待时间较短的场合
b- 自旋锁可能造成死锁,使用的临街区域不能有阻塞(copy_from_user()、copy_to_user()、kmalloc)())、递归调用等。

4- 信号量:信号量和自旋锁类似,只是在获取不到信号量会进入睡眠状态。信号量可用于同步,即A任务需要在B任务执行完才动作,A任务等待获取信号量,B任务执行完释放信号量。
注意事项:
a- 当临界区占用资源时间较长时适合使用信号量
b- 相较于自旋锁,信号量临界区可以使用阻塞代码
c- 信号量用于保存进程上下文,在中断、软中断中的临界资源不可使用信号量(信号量等待会使得等待任务睡眠),可使用自旋锁代替(部分资料中说中断中使用自旋锁前要禁止中断使能)。
 

没有更多推荐了,返回首页