信号概述
- 信号是UNIX中所使用的进程通信的一种最古老的方法。它是在软件层次上对中断机制的一种模拟,是一种异步通信方式。信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它为止。
-
一个完整的信号生命周期可以分为3个重要阶段,这3个阶段由4个重要事件来刻画的:包括信号产生、信号在进程中注册、信号在进程中注销、执行信号处理对应的函数 。
信号常见的处理函数
-
信号的处理包括信号的发送、捕捉和处理,它们有各自相对应的常见函数:
发生信号的函数: kill()、raise()。
捕捉信号的函数: alarm()、pause()。
处理信号的函数: signal()、sigaction()。
- 信号发送函数
- kill()函数同读者熟知的kill系统命令一样,可以发送信号给进程或进程组(实际上,kill系统命令只是kill()函数的一个用户接口)。这里需要注意的是,它不仅可以中止进程(实际上发出SIGKILL信号),也可以向进程发送其他信号。
-
与kill()函数所不同的是,raise()函数允许进程向自身发送信号。
- 代码实战
kill_raise.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<signal.h>
int main()
{
int ret;
pid_t pid,pid_wait;
pid=fork();
if(pid<0)
{
printf("fork error!\n");
return -1;
}
else if(pid==0)
{
printf("In child(%d) process,waiting for any signal\n",getpid());
raise(SIGSTOP);
exit(0);
}
else
{
if(pid_wait=waitpid(pid,NULL,WUNTRACED))
{
printf("Child(%d) is stop\n",pid_wait);
if((ret=kill(pid,SIGKILL))==0)
printf("In father process:\nParent kill process(%d)\n",pid);
}
/*
if((waitpid(pid,NULL,WNOHANG))==0)
{
if((ret=kill(pid,SIGKILL))==0)
printf("In father process:\nParent kill process(%d)\n",pid);
}
*/
waitpid(pid,NULL,0);
exit(0);
}
return 0;
}
-
信号捕捉函数
alarm()也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送SIGALARM信号。如果不忽略或不捕捉此信号,则其默认动作是终止该进程。要注意的是,一个进程只能有一个闹钟时间,如果在调用alarm()之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。
pause()函数是用于将调用进程挂起直至捕捉到信号为止。这个函数很常用,通常可以用于判断信号是否已到。
-
信号处理函数
信号处理的主要方法有两种,一种是使用简单的signal()函数,另一种是使用信号集函数组。
信号响应方式
- 用户进程对信号的响应可以有3种方式。
忽略信号,即对信号不做任何处理,但是有两个信号不能忽略,即SIGKILL及SIGSTOP。
捕捉信号,定义信号处理函数,当信号发生时,执行相应的自定义处理函数。
执行缺省操作,Linux对每种信号都规定了默认操作。
共享内存
- 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
- 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
- 进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
- 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
-
Shell的ipcs命令可以查看共享内存情况
-
共享内存使用步骤
1)创建/打开共享内存;
2)映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问;
3)分离/撤销共享内存映射;
4)删除共享内存对象;
-
共享内存创建
key : 和信号量一样,程序需要提供一个参数key,它有效地为共享内存段命名。有一个特殊的键值IPC_PRIVATE, 它用于创建一个只属于创建进程的共享内存,仅用于有亲缘关系的进程间通信。
size: 以字节为单位指定需要共享的内存容量。
shmflag: 包含9个比特的权限标志,它们的作用与创建文件时使用的mode标志是一样。
读写的权限还有IPC_CREAT或IPC_EXCL对应文件的O_CREAT或O_EXCL
权限标志对共享内存非常有用,因为它允许一个进程创建的共享内存可以被共享内存的创建者所拥有的进程写入,同时其它用户创建的进程只能读取共享内存。
我们可以利用这个功能来提供一种有效的对数据进行只读访问的方法,通过将数据放共享内存并设置它的权限,就可以避免数据被其他用户修改。 - ftok
同一段程序,为保证两个不同用户下的两组相同程序获得互不干扰的IPC键值,通常利用ftok函数将文件节点和一个id值组合生成一个唯一的IPC键值。
ftok原型如下:key_t ftok( char * fname, int id )
fname就时你指定的文件名(该文件必须是存在而且可以访问的),
id是子序号,虽然为int,但是只有8个比特被使用(0-255)。
当成功执行的时候,一个key_t值将会被返回,否则 -1 被返回。
使用 ftok创建共享内存,毫无关系的进程也可以通过得到同样的key,来操作同一个共享内存,在对共享内存进行读写时,需要利用信号量进行同步或互斥。
使用IPC_PRIVATE创建的IPC对象, 和无名管道类似,只可以用于有亲缘关系的进程间通信。 -
共享内存映射函数
作用:将共享内存映射到本进程地址空间,以实现本进程对该共享内存区的访问。
注意:共享内存的读写权限由它的属主(共享内存的创建者)决定,它的访问权限由当前进程的属主决定。
-
共享内存分离
作用:将共享内存从当前进程空间分离。
注意:共享内存分离并未删除它,只是使得该共享内存对当前进程不再可用。
-
共享内存控制
函数作用:实现对共享内存的控制
shmid_ds结构至少包含以下成员:
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
- 共享内存代码实战:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define BUFFER_SIZE 1024
struct share_mm//共享内存结构体
{
int flag_wrote;
char buf[BUFFER_SIZE];
};
int main()
{
int shmid;
pid_t pid;
struct share_mm * shmaddr;//保存映射地址
//创建共享内存
shmid = shmget(IPC_PRIVATE,sizeof(struct share_mm),0666);
if(shmid==-1)
{
printf("shmget error\n");
exit(1);
}
else
{
printf("Shmid is %d\n",shmid);
system("ipcs -m");
}
//创建子进程
pid = fork();
if(pid==-1)
{
printf("fork error\n");
exit(1);
}
else if(pid==0)//in child process
{
shmaddr=shmat(shmid,0,0);//映射,并获得映射地址
if(shmaddr==(void *)-1)
{
printf("shmat error\n");
exit(1);
}
else
{
printf("Child attach shm is %p\n",shmaddr);
system("ipcs -m");
}
do
{
if(shmaddr->flag_wrote!=1)//判断父进程是否写数据到共享内存
{
printf("Wait father write message!!\n");
while(shmaddr->flag_wrote!=1);//等待父进程写数据
printf("From father message:%s\n",shmaddr->buf);
shmaddr->flag_wrote=0;//标记数据已读走
}
}while(strncmp(shmaddr->buf,"quit",4));
printf("Father byebye\n");
if((shmdt(shmaddr))<0)//删除地址映射
{
printf("shmdt error\n");
exit(1);
}
exit(0);
}
else //in fater process
{
shmaddr=shmat(shmid,0,0);
if(shmaddr==(void *)-1)
{
printf("shmat error\n");
exit(1);
}
else
{
printf("Father attach shm is %p\n",shmaddr);
system("ipcs -m");
}
shmaddr->flag_wrote=0;
do
{
if(shmaddr->flag_wrote==0)//判断数据是否被子进程读走
{
memset((void *)shmaddr->buf,0,BUFFER_SIZE);
printf("In father process:\nPlease write message\n");
gets(shmaddr->buf);//从键盘写入数据到共享内存
shmaddr->flag_wrote=1;//标记已写数据
}
}while(strncmp(shmaddr->buf,"quit",4));
waitpid(pid,NULL,0);//等待子进程先退出
printf("Child byebye\n");
if((shmdt(shmaddr))<0)
{
printf("shmdt error\n");
exit(1);
}
if((shmctl(shmid,IPC_RMID,NULL))<0)//删除内核中的共享内存
{
printf("shmctl error\n");
exit(1);
}
exit(0);
}
return 0;
}