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

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

    7036 人正在学习 去看看 良许

信号传递过程

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

    以上便是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-07-31 15:45:15 weixin_33398032 阅读数 245
  • 玩转Linux:常用命令实例指南

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

    7036 人正在学习 去看看 良许

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-03-16 23:47:04 qq_29924041 阅读数 1720
  • 玩转Linux:常用命令实例指南

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

    7036 人正在学习 去看看 良许

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 阅读数 61
  • 玩转Linux:常用命令实例指南

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

    7036 人正在学习 去看看 良许

信号

一 、 概念和功能:

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

二 、 查看信号:

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

2016-12-26 15:06:47 PZ0605 阅读数 704
  • 玩转Linux:常用命令实例指南

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

    7036 人正在学习 去看看 良许

回顾:

  1. linux内核底半部的工作队列
    明确:linux内核底半部的机制有哪些?
    tasklet:tasklet对应的延后执行函数工作中断上下文中,并且tasklet本身也是基于软中断实现,所以不能进行休眠操作
    工作队列:工作队列对应的延后执行函数工作在进程上下文中,所以可以进行休眠操作
    软中断:对应的延后执行函数以不能以模块的形式实现,必须静态编译到内核中;并且要求延后执行函数具备可重入性。
  2. 强调一点是底半部机制并不是发彩要和顶半部中断处理函数配合使用,底半部机制本质就是延后执行的一种手段而已!
  3. linux内核一此关于时间的概念
    系统定时器硬件:能够通过编程来设置它的工作输出频率,并且能够周期性,稳定的给CPU产生一个时钟中断信号。
    系统定时器硬件对应的时钟中断处理函数:
    更新jiffies
    更新实际时间
    检查时间片
    检查是否有超时的内核定时器

    HZ:常数,对于RMA来说,HZ=100,一秒钟100次时钟中断
    tick:
    jiffies:内核全局变量,没发生一次时钟中断,自动加1

    linux内核的软件定时器
    数据结构:struct timer_list
    成员:
    expires:
    function:
    data:
    操作方法:
    init_timer
    add_timer
    del_timer
    mod_timer
    切记:定时器本身基于软中断实现,要求对应的超时处理函数不能做休眠!

linux内核的延时方法:
忙延时:
ndelay
udelay
mdelay
“bogomips”:CPU一秒钟做多少百万条指令
休眠延时:
msleep()
ssleep()
schedule
shcedule_timeout

linux内核的并发和竞态:
案例:要求一个硬件设备只能被一个应用软件所打开访问操作!

概念:
并发:多个执行单元同时发生
“执行单元”:硬件中断、软中断、进程
竞态:多个执行单元同时访问共享资源产生竞态
产生竞态的条件:
1,必须有多个执行单元
2,必须有共享资源
3,必须同时访问
共享资源:硬件资源(驱动程序中但凡设计的寄存器都是共享资源)和软件上的全局变量
互斥访问:当有多个执行单元要访问共享资源的时候,只允许一个执行单元访问共享资源,其他执行单元禁止访问!
临界区:访问共享资源的代码区域

if(--open_cnt !=0){
   ...
   ++open_cnt;
   ...
}

互斥访问的本质目的:就是让临界区的代码执行路径具有原子性!

2,一定要明确在哪些场合会形成竞态?
多核:因为多核会共享内存,外存,IO,如果同时访问,势必形成竞态;
进程与进程的抢占;
中断和进程;
中断和中断;

3,linux内核解决竞态的方法
中断屏蔽:
能够解决哪些场合的竞态问题?
答:进程和进程的抢占(依赖中断来实现)
中断和进程
中断和中断
注意:中断屏蔽的是当前CPU的中断信号
中断屏蔽的相关使用方法:
1,在访问临界区之前屏蔽中断
unsigned long flags;
local_irq_disable();//仅仅屏蔽中断
或者
local_irq_save(flags);//屏蔽中断,保存中断状态到flags中断
2,执行临界区的代码
切记:临界区的代码要求执行速度要快,千万不能做休眠操作
3,在访问临界区之后恢复中断
local_irq_enable();//仅仅恢复中断
或者
local_irq_restore(flags);//恢复中断,从flags中恢复保存的中断状态

参考代码:
static int open_cnt = 1;
int led_open(struct inode *inode,struct file *file){
    unsigned long flags;
    //屏蔽中断
    local_irq_save(flags);
    if(--open_cnt!=0){
       ++open_cnt;
       //恢复中断
       local_irq_restore(flags);
       return -EBUSY;
    }
    //恢复中断
    local_irq_restore(flags);
    return 0;
}

注意:屏蔽中断与恢复中断一定要成对出现
切记:中断屏蔽保护临界区要求代码的执行速度要快,不能做休眠操作。

linux内核原子操作:
问:原子操作能够解决哪些场合的竞态问题?
答:原子操作能够解决所有的竞态问题!

原子操作分为两类操作:
位原子操作:位操作+操作过程具有原子性
使用场合:如果在设备驱动开发时,代码中有共享资源,并且对共享资源进行位操作,并且这个过程有竞态问题,此时可以选择位原子操作来解决竞态问题!

例如:

static int open_cnt = 1;//共享资源
int led_open(struct inode *inode,struct file *file){
    open_cnt &= ~0x1;//把bit1清0,这行代码不具体原子性
    return 0;
}

解决方法:
方法1:采用中断屏蔽

static int open_cnt = 1;//共享资源
int led_open(struct inode *inode,struct file *file){
    unsigned long flags;
    local_irq_save(flags);
    open_cnt &= ~0x1;//代码具体原子性
    local_irq_restore(flags);
    return 0;
}//这种方法无法解决多核引起的竞态

方法二:采用位原子操作:
linux内核提供了位原子操作的相关方法:
set_bit/clear_bit/change_bit/test_bit组合函数

位原子操作:

static int open_cnt = 1;//共享资源
int led_open(struct inode *inode,struct file *file){
    clear_bit(0,&open_cnt);//第一个bit位清0
    return 0;
}

整形原子操作:整形操作+操作过程具有原子性
使用场合:如果在设备驱动开发时,代码中有共享资源,并且对共享资源进行整形操作,并且这个过程有竞态问题,此时可以选择整形原子操作来解决竞态问题!
linux内核对于整形原子操作提供的相关方法:
整形原子变量的数据类型:atomic_t
1,分配整型原子变量
atomic_t v;
2,内核对整型原子变量提供的方法
atomic_set/atmoic_read/atmoic_add/atmoic_sub/atomic_inc/atmoic_dec组合函数
参考代码:

static int open_cnt = 1;//共享资源
int led_open(struct inode *inode,struct file *file){
    unsigned long flags;
    if(--open_cnt!=0){//临界区
       ++open_cnt;
       return -EBUSY;
    }
    return 0;
}

分析:
–open_cnt:这个整形操作不具备原子性
解决方法:
方法1:采用中断屏蔽,问题是不能解决多核问题!
方法2:采用整形原子操作,代码修改如下:

static atomic_t open_cnt = ATOMIC_INIT(1);//atomic_t,声明一个整形原子变量
int led_open(struct inode *inode,struct file *file){
    unsigned long flags;
    if(!atmoic_dec_and_test(&open_cnt)){//具有原子性
       ++open_cnt;
       return -EBUSY;
    }
    return 0;
}

注意:原子操作涉及的相关操作方法,如果对于多核情况,这些函数方法会使用ARM两条原子指令:ldrex,strex
如果一个CPU在处理ldrex,strex的代码时,其他CPU原地空转!等待前一个CPU执行完毕!

案例:要求LED设备只能被一个应用软件打开访问操作!(中断屏蔽(解决不了多核的竞态)、原子操作)
open->led_open->struct file_operation->struct cdev 或者 struct miscdevice

led_dev.c

#include <linux/init.h>
#include <linux/moudule.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>

//共享资源
static int open_cnt = 1;

static int led_open(struct innode *node,struct file *file){
    unsigned long flags;
    //屏蔽中断
    local_irq_save(flags);
    if(--open_cnt != 0){
        printk("设备已经被打开了。。。\n");
        open_cnt ++;
         //恢复中断
        local_irq_restore(flags);
        return -EBUSY;
    }

    //恢复中断
    local_irq_restore(flags);
    printk("设备打开成功。。。");
    return 0;
}

static int led_close(struct innode *node,struct file *file){
    unsigned long flags;
    local_irq_save(flags);
    open_cnt ++;
    local_irq_restore(flags);
    return 0;
}

//分配硬件操作接口
static struct file_operations led_fops ={
     .owner = THIS_MODULE,
     .open = led_open,
     .release = led_close
};

//分配混杂设备对象
static struct miscdevice led_misc = {
     .minor = MISC_DYNAMDIC_MINOR,//动态分配次设备号
     .name = "myled",//dev/myled
     .fops = &led_fops
}

static int  led_init(void){
     //注册混杂设备
     misc_register(&led_misc);
     return 0;
}

static void  led_exit(void){
      misc_deregister(&led_misc);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

原子操作除了可以采用上面的中断屏蔽以外,还可以采用整形原子变量。

应用程序测试,led_test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt1.h>

int main(int argc,char *argv[]){
    int fd;
    fd = open("/dev/myled",o_RDWR);
    if(fd<0){
        printf("打开设备失败\n");
        return -1;
    }

    //打开设备成功后,进入休眠状态
    sleep(1000);
    close(fd);
    return 0;
}

实验的步骤:
inmod led_drv.ko
ls /dev/myled -lh
./led_test & //启动A进程
ps //查看进程的PID
top //查看进程的状态:休眠状态s
./led_test //启动B进程


linux内核互斥机制—–自旋锁

自旋锁的特点:自旋+锁
仅仅只有自旋锁是没有意义的,它必须附加在某个共享资源上。
自旋锁在同一时刻只能被一个执行单元进行持有!
如果一个执行单元获取不了自旋锁,将会原地打转!

linux内核自旋锁的数据类型:spinlock_t

问:自旋锁如何保护临界区呢?
答:
1,分配自旋锁
spinlock_t lock;
2,初始化自旋锁
spinlock_init(&lock);
3,访问临界区之前,获取自旋锁
spin_lock(&lock);//如果获取自旋锁,立即返回,执行临界区的代码;如果获取不了自旋锁,任务将会在此忙等待,直到持有自旋锁的任务释放自旋锁!
或者:
spin_try_lock(&lock);//尝试获取自旋锁,如果获取自旋锁返回true,才有资格访问临界区,否则返回false,就没有资格访问临界区,注意在使用的时候,一定要对其返回值进行判断
4,执行临界区代码
5,释放自旋锁
spin_unlock(&lock);//一旦释放自旋锁,等待自旋锁的任务也将会获取自旋锁
6,切记:
以上自旋锁的操作过程只能解决多核和进程与进程之间的抢占引起的竞态问题,无法解决中断引起的竞态问题!

问:如果利用自旋锁要解决中断引起的竞态问题,那么怎么办?
答:可以利用衍生自旋锁
衍生自旋锁的特点:其实就是在自旋锁的基础上加了中断屏幕的功能而已!

衍生自旋锁的使用
1,分配自旋锁
spinlock_t lock;
2,初始化自旋锁
spinlock_init(&lock);
3,访问临界区之前,获取自旋锁
unsigned long flags;
spin_lock_irq(&lock);//如果获取自旋锁,还会屏蔽中断,立即返回,执行临界区的代码;如果获取不了自旋锁,任务将会在此忙等待,直到持有自旋锁的任务释放自旋锁!
或者:
spin_lock_irqsave(&lock,flags);//尝试获取自旋锁,还会屏蔽中断,如果获取自旋锁返回true,才有资格访问临界区,否则返回false,就没有资格访问临界区,注意在使用的时候,一定要对其返回值进行判断
4,执行临界区代码
5,释放自旋锁
spin_unlock_irq(&lock);//一旦释放自旋锁,恢复中断,等待自旋锁的任务也将会获取自旋锁
或者
spin_unlock_irqrestore(&lock,flags);//释放自旋锁,恢复中断
6,切记:衍生自旋锁能够解决所有的竞态问题
总结:
使用普通的自旋锁,如果临界区的执行时间较长,会降低系统的性能!
使用衍生自旋锁,如果临界区的执行时间较长,不仅仅会降低系统的性能,甚至会造成数据丢失或者系统的崩溃!
所有使用自旋锁,要求临界区的代码执行速度要快,更不能做休眠动作!

案例:利用自旋锁实现一个设备只能被一个应用软件打开访问操作!

#include <linux/init.h>
#include <linux/moudule.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>

//共享资源
static int open_cnt = 1;
//分配自旋锁
static spinlock_t lock;

static int led_open(struct innode *node,struct file *file){

    unsigned long flags;//保存中断状态
    //获取自旋锁,屏蔽中断,保存中断状态
    spin_lock_irqsave(&lock,flags);
    if(--open_cnt != 0){
        printk("设备已经被打开了。。。\n");
        open_cnt ++;
        //释放自旋锁,恢复中断和中断状态
        spin_unlock_irqrestore(&lock,flags);
        return -EBUSY;
    }
    //释放自旋锁,恢复中断和中断状态
    spin_unlock_irqrestore(&lock,flags);
    printk("设备打开成功 \n");
    return 0;
}

static int led_close(struct innode *node,struct file *file){
    unsigned long flags;
   //获取自旋锁,屏蔽中断,保存中断状态
    spin_lock_irqsave(&lock,flags);
    open_cnt ++;
    //释放自旋锁,恢复中断和中断状态
    spin_unlock_irqrestore(&lock,flags);
    return 0;
}

//分配硬件操作接口
static struct file_operations led_fops ={
     .owner = THIS_MODULE,
     .open = led_open,
     .release = led_close
};

//分配混杂设备对象
static struct miscdevice led_misc = {
     .minor = MISC_DYNAMDIC_MINOR,//动态分配次设备号
     .name = "myled",//dev/myled
     .fops = &led_fops
}

static int  led_init(void){
     //注册混杂设备
     misc_register(&led_misc);
     //初始化自旋锁
     spin_lock_init(&lock);
     return 0;
}

static void led_exit(void){
      misc_deregister(&led_misc);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

linux内核信号量互斥机制:
进程的状态:
1,进程的准备就绪状态:TASK_READY
2,进程的运行状态:TASK_RUNNING
3,进程的休眠状态:
进程的可中断休眠状态:TASK_INTERRUPTIBLE,如果给进程发送信号,这个进程立即被唤醒并且处理这个信号。
进程的不可中断休眠状态:TASK_UNINTERRUPTIBLE,如果给进程发送信号,这个进程不会立即处理信号,一旦进程被唤醒,进程将会处理这个信号。

信号量特点:
有别名睡眠锁!
持有信号量的任务在执行临界区的代码可以进行休眠操作!获取信号量的任务在没有获取信号量也可以进入休眠状态!
信号量的数据结构:struct semaphore

问:如何使用信号量保护临界区呢?
1,分配信号量
struct semaphore sema;
2,初始化信号量为互斥信号量
sema_init(&sema,1);
3,访问临界区之前获取信号量
down(&sema);//如果获取信号量,立即返回;如果没有获取信号量,进程将进入不可中断的休眠状态!
或者
down_interruptible(&sema);//获取信号量,立即返回0,如果没有获取信号量,进程将进入可中断的休眠状态!一旦休眠的进程被唤醒(信号量可用),休眠的进程立即获取信号量,此函数返回0,如果返回非0,表明这个进程接收到信号引起的唤醒!
或者
down_trylock(&sema);//获取信号量返回0,否则返回非0,不会引起进程的休眠
4,执行临界区的代码
5,释放信号量,并且唤醒休眠的进程
up(&sema);

案例:利用信号量,实现一个设备只能被一个应用软件访问操作!

#include <linux/init.h>
#include <linux/moudule.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>

//分配信号量
static struct semphore sema;

static int led_open(struct innode *node,struct file *file){
    if(down_trylock(&sema)){
        printk("设备打开失败 \n");
        return -EBUSY; 
    }
    printk("设备打开成功 \n");
    return 0;
}

static int led_close(struct innode *node,struct file *file){
    //释放信号量
    up(&sema);//释放信号量并且唤醒休眠的进程
    return 0;
}

//分配硬件操作接口
static struct file_operations led_fops ={
     .owner = THIS_MODULE,
     .open = led_open,
     .release = led_close
};

//分配混杂设备对象
static struct miscdevice led_misc = {
     .minor = MISC_DYNAMDIC_MINOR,//动态分配次设备号
     .name = "myled",//dev/myled
     .fops = &led_fops
}

static int  led_init(void){
     //注册混杂设备
     misc_register(&led_misc);
     //初始化信号量为互斥信号量
     sema_init(&sema,1);
     return 0;
}

static void led_exit(void){
      misc_deregister(&led_misc);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
没有更多推荐了,返回首页