Linux:信号(下)

    在Linux:信号上博文中我们写了一个mysleep,但是实际上这个函数在多线程环境下是会出现错误的,也就是我们这个mysleep函数并不是可重入函数,

现在重新审视“mysleep”程序,设想这样的时序:

1. 注册SIGALRM信号的处理函数。

2. 调用alarm(nsecs)设定闹钟。

3. 内核调度优先级更高的进程取代当前进程执行,并且优先级更高的进程有很多个,每个都要执行很长时间

4. nsecs秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态。

5. 优先级更高的进程执行完了,内核要调度回这个进程执行。SIGALRM信号递达,执行处

理函 数sig_alrm之后再次进入内核。

6. 返回这个进程的主控制流程,alarm(nsecs)返回,调用pause()挂起等待。

7. 可是SIGALRM信号已经处理完了,还等待什么呢?

    出现这个问题的根本原因是系统运行的时序(Timing)并不像我们写程序时所设想的那样。虽然alarm(nsecs)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(nsecs)之 后的nsecs秒之内被调用。由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件(RaceCondition)。

其实就是在操作系统中的进程执行序列的优先级抢占。或者是进程中的当前执行时间片完成以后进行的进程中断。

    如何解决上述问题呢?读者可能会想到,在调用pause之前屏蔽SIGALRM信号使它不能提前递达就可以了。看看以下方法可行吗?

1. 屏蔽SIGALRM信号;

2. alarm(nsecs);

3. 解除对SIGALRM信号的屏蔽;

4. pause();

    从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。要消除这个间隙, 我们把解除屏蔽移到pause后面可以吗?

1. 屏蔽SIGALRM信号;

2. alarm(nsecs);

3. pause();

4. 解除对SIGALRM信号的屏蔽;

    这样更不行了,还没有解除屏蔽就调用pause,pause根本不可能等到SIGALRM信号。要是“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作就好了,这正是sigsuspend函数的功能。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause。

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);


和pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某 个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原

来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

下面来看一下修改以后的mySleep:

#include<stdio.h>
#include<signal.h>
void handle(int sig)
{
     printf("i get a sig %d\n", sig);
}
int sleep(int time)
{
    struct sigaction oldact,newact;
    sigset_t newmask,oldmask,suspmask;
    newact.sa_handler=handle;
    newact.sa_flags=0;
    sigemptyset(&newact.sa_mask);
    sigaction(SIGALRM,&newact,&oldact);//注册SIGALRM的信号处理函数
    sigemptyset(&newmask);
    sigaddset(&newmask,SIGALRM);
    sigprocmask(SIG_BLOCK,&newmask,&oldmask);//屏蔽SIGALRM信号;
    alarm(time);
    suspmask=oldmask;
    sigdelset(&suspmask,SIGALRM);//解除suspmask中SIGALRM信号的屏蔽;
    sigsuspend(&suspmask);//用suspmask去替换PCB中的block表,从而解除对SIGALRM信号的阻塞
    int ret=alarm(0);
    sigaction(SIGALRM,&oldact,NULL);
    sigprocmask(SIG_SETMASK,&oldmask,NULL);//恢复之前的系统默认处理信号方式
    return ret;
}
int main()
{
    while(1)
    {
        printf("I am sleep\n");
        sleep(5);
    }
    return 0;
}

下面我们来着重讲解一个信号SIGCHLD:

    SIGCHLD就是子进程结束时对父进程的返回信号,但是对于这个信号,系统的默认动作是忽略。

在之前的进程中,父子进程都是使用waitpid,wait函数对僵尸进程进行清理,父进程以阻塞/非阻塞方式对子进程进行结束等待清理,但是这样的话会加大父进程的执行压力。

    所以我们利用子进程结束时传递的SIGCHLD信号来通知父进程,这样会提高父子进程之间的运行效率,传递SIGCHLD后进行对子进程的清理:

代码:

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void my_sigchld(int sig)
{
    int status=0;
    pid_t ret;
     while((ret=waitpid(-1,&status,0))>0)
    {
        printf("sig: %d,code: %d\n",status&0xff,(status>>8)&0xff);
    }
}
int main()
{
    pid_t tid=fork();
    if(tid<0)
    {
        perror("fork");
        exit(1);
    }
    else if(tid==0)
    {
         sleep(10);//保证父进程已注册完信号处理函数,父,子进程谁先运行不确定
         printf("child is quit!\n");
         exit(1);
    }
    else
    {
         signal(SIGCHLD,my_sigchld);
         while(1);
    }
    return 0;
}

注意一点:在my_sigchld中,注意到while循环来对waitpid进行等待,保证在多进程环境中对于子进程的清理,若是if而不是while的话,因为SIGCHLD信号无法的多次收取,未决存在中只能够证明有与无,所以对于父进程来说。我们需要不断的waitpid进行循环等待,知道等待失败为止。

本文出自 “剩蛋君” 博客,请务必保留此出处http://memory73.blog.51cto.com/10530560/1771250

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 中,可以使用 `semctl` 系统调用中的 `GETVAL` 命令来获取指定信号量的值。如果该值为0,则表示信号量已经被占用,没有可用的资源。 具体的代码实现可以参考如下示例: ```c #include <stdio.h> #include <stdlib.h> #include <sys/sem.h> int main() { int semid; struct sembuf sb; // 创建信号量集 semid = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); } // 初始化信号量 if (semctl(semid, 0, SETVAL, 1) == -1) { perror("semctl"); exit(EXIT_FAILURE); } // 获取信号量的值 printf("Sem value: %d\n", semctl(semid, 0, GETVAL)); // 尝试获取信号量 sb.sem_num = 0; sb.sem_op = -1; // P 操作 sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop"); exit(EXIT_FAILURE); } printf("Semaphore acquired\n"); // 释放信号量 sb.sem_op = 1; // V 操作 if (semop(semid, &sb, 1) == -1) { perror("semop"); exit(EXIT_FAILURE); } printf("Semaphore released\n"); // 删除信号量集 if (semctl(semid, 0, IPC_RMID, 0) == -1) { perror("semctl"); exit(EXIT_FAILURE); } return 0; } ``` 在这个示例中,我们首先使用 `semget` 系统调用创建了一个信号量集,并使用 `semctl` 系统调用对该信号量进行了初始化。然后使用 `semctl` 系统调用获取了该信号量的值,并输出到控制台。 接下来,我们使用 `semop` 系统调用进行了一次 P 操作,即尝试获取该信号量。如果该信号量的值为0,则该操作将会被阻塞,直到信号量的值变为非0。在本例中,由于我们已经将信号量初始化为1,因此该 P 操作可以顺利完成。 最后,我们使用 `semop` 系统调用进行了一次 V 操作,即释放该信号量。然后使用 `semctl` 系统调用删除了该信号量集。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值