马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 moc 于 2018-11-15 17:03 编辑
1、信号在linux中的传递过程
信号递达(Delivery): 执行信号的处理动作称为。
信号未决(Pending):信号从产生到递达之间的状态。
进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意,阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
信号在linux中的传递过程:
说明1: PCB进程控制块中函数有信号屏蔽状态字(block)信号未决状态字(pending)还有是否忽略标志;
说明2: 信号屏蔽状态字(block),1代表阻塞、0代表不阻塞;信号未决状态字(pending)的1代表未决,0代表信号可以抵达了;
说明3: 向进程发送SIGINT,内核首先判断信号屏蔽状态字是否阻塞,若阻塞则信号未决状态字相应位制成1;若阻塞解除,信号未决状态字相应位制成0;表示信号可以抵达了。
说明4: block状态字、pending状态字 都是64bit;
说明5: block状态字用户可以读写,pending状态字用户只能读, 这是信号设计机制。
信号集操作函数(状态字表示):
头文件: #include <signal.h>
原型 | 功能
| int sigemptyset(sigset_t *set) | 把信号集64bit全设为0
| int sigfillset(sigset_t *set) | 把信号集全设为1
| int sigaddset(sigset_t *set, int signo) | 根据signo,把信号集中的对应为置成1
| int sigdelset(sigset_t *set, int signo) | 根据signo,把信号集中的对应为置成0
| int sigismember(const sigset_t *set, int signo) | 判断signo是否在信号集中
| int sigprocmask(int how, const sigset_t *set, sigset_t *oset) | 读取或更改进程的信号屏蔽状态字(block)
| int sigpending(sigset_t *set) | 获取信号未决状态字(pending)信息 |
sigprocmask函数:
功能: 读取或更改进程的信号屏蔽字。
返回值: 若成功则为0,若出错则为-1
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
// 演示信号从产生到抵达的整个过程
// 信号的阻塞和解除阻塞的综合实验
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0);
// 信号处理函数
void handlder(int sig)
{
if (sig == SIGINT)
{
printf("recv a sig = %d \n", sig);
}
else if (sig == SIGQUIT)
{
sigset_t uset; // 自建一个信号集
sigemptyset(&uset); // 清空信号集
sigaddset(&uset, SIGINT);
// Ctrl + \ 来解除 SIGINT 信号
// 解除阻塞
sigprocmask(SIG_UNBLOCK, &uset, NULL); // 更改信号屏蔽字block
}
}
// 打印信号集
void printsigset(sigset_t *set)
{
int i;
for (i=1; i<NSIG; ++i)
{
if (sigismember(set, i)) // 查看信号集中是否有i信号
{
putchar('1');
}
else
{
putchar('0');
}
}
printf("\n");
}
// 连续按键盘 Ctrl + c ,虽然发送了多个SIGINT信号,但因为信号是不稳定的,只保留了一个
// 不支持排队 PCB ==> task_struct
int main(int argc, char *argv[])
{
sigset_t pset; // 用来打印信号集 未决状态字
sigset_t bset; // 用来设置阻塞信号集
sigemptyset(&bset);
sigaddset(&bset, SIGINT); // 把信号集中相应的位置1
// 注册信号
if (signal(SIGINT, handlder) == SIG_ERR)
{
ERR_EXIT("signal error");
}
if (signal(SIGQUIT, handlder) == SIG_ERR)
{
ERR_EXIT("signal error");
}
// 读取或更改进程信号的屏蔽字 这里来阻塞信号Ctrl + c信号
// Ctrl + c信号被设置从阻塞, 即使用户按下ctrl+c键,也不会抵达
sigprocmask(SIG_BLOCK, &bset, NULL); // 或的方式设置阻塞位
for (;;)
{
// 获取未决 状态字信息
sigpending(&pset);
// 打印信号未决 sigset_t 字
printsigset(&pset);
sleep(2);
}
return 0;
}
2、sigaction函数注册信号处理函数、sigqueue新的信号发送函数
可带值的信号传递。
1. sigaction函数
功能: sigaction函数用于改变进程接收到特定信号后的行为。
原型: int sigaction(int signum,const struct sigaction *act,const struct sigaction *old);
参数: 1> 第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一 个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误);
2> 第二个参数是指向结构sigaction的一个实例的指针,在结构 sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;
3> 第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。
返回值: 函数成功返回0,失败返回-1.
2. sigaction结构体struct sigaction {
void (*sa_handler)(int); // 此参数和signal()的参数handler相同,代表新的信号处理函数,不接受额外数据
void (*sa_sigaction)(int, siginfo_t *, void *); // 信号处理程序 能接受额外数据,和sigqueue配合使用
sigset_t sa_mask; // 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置,信号最终还会抵达
int sa_flags; // //影响信号的行为, 可选SA_SIGINFO表示能接受数据
}
注意: 回调函数句柄sa_handler、sa_sigaction只能任选其一。
3. siginfo_t结构体siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void * si_ptr; /* POSIX.1b signal */
void * si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
sigqueue函数
功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用。
原型: int sigqueue(pid_t pid, int sig, const union sigval value);
参数: 第1个参数是指定接收信号的进程id,
第2个参数确定即将发送的信号,
第3个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
返回值: 成功返回0,失败返回-1
注意: 和kill函数相比Int kill(pid_t pid, int siq)多了参数;sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
sigval联合体typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
// 演示发送和接受带数据的信号
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0);
typedef struct Teacher
{
char name[64];
int age;
}Teacher;
// 用新的回调函数接受数据....
void myHandle_forsigaction(int signum, siginfo_t *s_t, void *p)
{
int myint = 0;
printf("可传输数据的回调函数.\n");
printf("recv signum : %d \n", signum);
myint = s_t->si_value.sival_int;
printf("%d %d \n", myint, s_t->si_int);
}
int main(int argc, char *argv[])
{
pid_t pid;
int ret = 0;
struct sigaction act;
act.sa_sigaction = myHandle_forsigaction;
sigemptyset(&act.sa_mask); // sa_mask集中的信号在处理sa_sigaction函数时会暂时阻塞
// 父进程的回调函数, 准备接受额外的数据
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGINT, &act, NULL) < 0)
{
ERR_EXIT("sigaction error");
}
pid = fork();
if (pid == -1)
{
perror("fork error");
return 0;
}
if (pid == 0)
{
union sigval mysigval;
mysigval.sival_int = 222;
ret = sigqueue(getppid(), SIGINT, mysigval);
if (ret != 0)
{
printf("sigqueue .....\n");
exit(0);
}
else
{
printf("sigqueue ...sucecess\n");
sleep(2);
}
}
else if (pid > 0)
{
for (;;)
{
pause();
}
}
return 0;
}
|