同主机进程间异步机制—信号(signal)
定义
信号(signal,称为软中断)机制是在软件层次上对中断机制的一种模拟 。
结构
该机制通常包括三部分:
- 信号的分类、产生和传送。
- 对各种信号预先规定的处理方式。
- 信号的检测和处理。
信号分类
进程对信号的处理方式
信号来源
- 进程彼此间也可用系统提供的系统调用发送信号
- 普通进程只能向具有相同uid和gid的进程发送信号或向相同进程组中的其他进程发送信号
处理方式
- 忽略信号。但不能忽略SIGKILL和SIGSTOP信号。
- 阻塞信号。进程可以选择对某些信号予以阻塞。
- 由进程处理该信号。用户在trap命令中可以指定处理信号的程序,从而进程本身可在系统中标明处理信号的处理程序的地址。当发出该信号时,就由标明的处理程序进行处理。
- 有系统进行默认处理。如上所述,系统内核对各种信号(除用户自定义之外)都规定了相应的处理程序。在默认情况下,信号就由内核处理,即执行内核预定的处理程序
进程对信号的处理流程
信号处理函数
信号发送
- kill、raise、alarm、ualarm
信号安装
- signal、sigaction
信号屏蔽
- sigprocmask
信号等待
- pause、sigsuspend
kill
- 功能:向指定进程发送信号
- extern int kill(pid_t pid,int sig)
- pid:发送信号的进程号;
- sig:发送的信号值。
- 返回值:=0,成功,=-1,出错。
- 注意
raise
- 功能:向当前进程发送信号,可以唤醒进程。
- extern int raise(int sig)
- sig:发送的信号值。
- 返回值:=0,成功,=-1,出错。
alarm
- 功能:定时产生一次SIGALRM信号。
- extern unsigned int alarm(unsigned int second)
- 返回值:=0,成功,=-1,出错。
#include<signal.h>
#include<stdio.h>
int main(void)
{
printf("first time return:%d\n",alarm(4));
sleep(1);
printf("after sleep(1),remain:%d\n",alarm(2));
printf("renew alarm,remain:%d\n",alarm(1));
}
ualarm
- 功能:在指定时间开始定时重复产生SIGALRM信号。
- extern usecond ualarm(usecond value,
usecond interval) - 举例:ualarm_exp.c
#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include<stdio.h>
void handler()
{
printf("int:hello\n");
}
int main()
{
int i;
signal(SIGALRM,handler);
printf("%d\n",ualarm(5000000,2000000));
//alarm(2);
while(1)
{
sleep(1);
printf("test\n");
}
}
signal
sigaction
struct sigaction
非亲缘进程间信号发送
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf("OUCH! - I got signal %d\n", sig);
}
int main()
{
(void) signal(SIGINT, ouch);
while(1) {
printf("Hello World!\n");
sleep(1);
}
}
亲缘进程间信号发送
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static int alarm_fired = 0;
void ding(int sig)
{
alarm_fired = 1;
}
int main()
{
int pid;
printf("alarm starting\n");
if((pid = fork()) == 0) {
sleep(5);
kill(getppid(), SIGALRM);
exit(0);
}
printf("waiting for alarm to go off\n");
(void) signal(SIGALRM, ding);
pause();
if (alarm_fired)
printf("Ding!\n");
printf("done\n");
exit(0);
}
信号集
举例:sig_set_member.c
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
int output(sigset_t set);
int main()
{
sigset_t set;
printf("after empty the set:\n");
sigemptyset(&set);
output(set);
printf("after add signo=2:\n");
sigaddset(&set,2);
output(set);
printf("after add signo=10:\n");
sigaddset(&set,10);
output(set);
sigfillset(&set);
printf("after fill all:\n");
output(set);
return 0;
}
int output(sigset_t set)
{
int i=0;
for(i=0;i<1;i++) //can test i<32
{
printf("0x%8x\n",set.__val[i]);
if((i+1)%8==0)
printf("\n");
}
}
屏蔽信号
- 信号忽略:系统仍然传递该信号,只是相应进程对该信号不作任何处理而已。
- 信号阻塞:系统不传递该信号,显示该进程无法接收到该信号直到进程的信号集发生改变。
sigprocmask设置进程阻塞的信号集
举例:sigprocmask_exp.c
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
static void sig_quit(int);
int main(int argc,char *argv[])
{
sigset_t newmask, oldmask, pendmask;
if (signal(SIGQUIT, sig_quit) == SIG_ERR)
{
perror("signal");
exit(EXIT_FAILURE);
}
printf("install sig_quit\n");
// Block SIGQUIT and save current signal mask.
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
{
perror("signal");
exit(EXIT_FAILURE);
}
printf("Block SIGQUIT,wait 15 second\n");
sleep(15); /* SIGQUIT here will remain pending */
if (sigpending(&pendmask) < 0)
{
perror("signal");
exit(EXIT_FAILURE);
}
if (sigismember(&pendmask, SIGQUIT))
printf("\nSIGQUIT pending\n");
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
{
perror("signal");
exit(EXIT_FAILURE);
}
printf("SIGQUIT unblocked\n");
sleep(15); /* SIGQUIT here will terminate with core file */
return 0;
}
static void sig_quit(int signo)
{
printf("caught SIGQUIT,the process will quit\n");
if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
{
perror("signal");
exit(EXIT_FAILURE);
}
}
等待信号
同主机进程间数据交互机制
无名管道(PIPE)
- 一个管道线就是连接两个进程的一个打开文件
- 由系统自动处理二者间的同步、调度和缓冲。管道文件允许两个进程按先入先出(FIFO)的方式传送数据,而彼此不知道对方的存在
- 每个管道只有一个内存页面用做缓冲区,该页面是按环型缓冲区的方式来使用的。
管道的创建
- 表头文件 #include<unistd.h>
- 定义函数 int pipe(int filedes[2]);
- 函数说明
- pipe()会建立管道,并将文件描述词由参数 filedes 数组返回。
- filedes[0]为管道里的读取端。
- filedes[1]则为管道的写入端
- 返回值: 若成功则返回零,否则返回-1,错误原因存于 errno 中。
- 错误代码
- EMFILE 进程已用完文件描述词最大量
- ENFILE 系统已无文件描述词可用。
- EFAULT 参数 filedes 数组地址不合法。
管道举例—pipeHalf.c
半双工
#include <unistd.h>
#include <stdio.h>
int main( void )
{
int filedes[2];
char buf[80];
pid_t pid;
pipe( filedes );
if ( (pid=fork()) > 0 )
{
printf( "father write a string \n" );
char s[] = "this is write by father\n";
write( filedes[1], s, sizeof(s) );
close( filedes[0] );
close( filedes[1] );
}
else
{
printf( "read a string \n" );
read( filedes[0], buf,sizeof(buf) );
printf( "%s\n", buf );
close( filedes[0] );
close( filedes[1] );
exit(0);
}
waitpid( pid, NULL, 0 );
exit(0);
}
管道举例—pipeTotal.c
全双工
#include <unistd.h>
#include <stdio.h>
int main( void )
{
int pipe1_fd[2],pipe2_fd[2];
char buf[80];
int len;
pid_t pid;
pipe(pipe1_fd);
pipe(pipe2_fd);
if ( (pid=fork()) > 0 )
{
close( pipe1_fd[0] );
close( pipe2_fd[1] );
char s[] = "this is write by father\n";
write( pipe1_fd[1], s, sizeof(s) );
len=read(pipe2_fd[0],buf,80);
buf[len] = ‘\0’;
printf("child is %s\n",buf);
}
else
{
close( pipe1_fd[1] );
close( pipe2_fd[0] );
len=read(pipe1_fd[0],buf,80);
buf[len] = ‘\0’;
printf("parent is %s\n",buf);
char s[] = "this is write by child\n";
write( pipe2_fd[1], s, sizeof(s) );
close(pipe1_fd[0]);
close(pipe2_fd[1]);
exit();
}
close(pipe1_fd[1]);
close(pipe2.fd[0]);
waitpid( pid, NULL, 0 );
exit();
}
利用管道在非亲缘进程间通信举例
伪代码
int main()
{ …………..
if (pipe(file_pipes) == 0) {
fork_result = fork();
………….
if (fork_result == 0) {
sprintf(buffer, "%d",
file_pipes[0]);
(void)execl("pipe4", "pipe4", buffer, (char *)0);
……….}
else {
data_processed =write(file_pipes[1],some_data, strlen(some_data));
}
}
……..
int main(int argc, char *argv[])
{
int data_processed;
char buffer[BUFSIZ + 1];
int file_descriptor;
memset(buffer, '\0', sizeof(buffer));
sscanf(argv[1], "%d", &file_descriptor);
data_processed =read(file_descriptor, buffer, BUFSIZ);
printf("%d - read %d bytes: %s\n", getpid(),
data_processed, buffer);
exit(EXIT_SUCCESS);
}
利用管道实现who|sort
if(pipe(fds)==-1)
{
perror("pipe");
exit(EXIT_FAILURE);
}
if (fork()== 0)
{
char buf[128];
dup2(fds[0], 0);
close(fds[1]);
execlp("sort", "sort", (char *)0);
}
else
{
if(fork() == 0){
dup2(fds[1], 1);
close(fds[0]);
execlp("who", "who", (char *)0);
}
else {
close(fds[0]);
close(fds[1]);
wait(NULL);
wait(NULL);
}
管道的特点
- 半双工:数据只能在一个方向流动。
- 只能在创建管道的进程祖先进程之中使用管道完成进程通信。
- 单独构成一个文件系统。
- 数据从管道尾端写入,从管道头部读出。
- 没有名字。
- 缓冲区大小受限。
- 管道传送的数据没有格式。
- 写入管道的数据读出就消失。
有名管道(FIFO)
命名管道创建
- 命令
mkfifo filename - 系统调用函数
- 头文件
#include <sys/types.h>
#include <sys/stat.h> - 函数原型
int mkfifo(const char *filename,mode_t mode); - 返回值:成功,返回文件描述符;否则,返回-1
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int res = mkfifo("/tmp/my_fifo", 0777);
if (res == 0)
printf("FIFO created\n");
exit(EXIT_SUCCESS);
}
- FIFO举例
- 亲缘进程通信举例
mkfifo_example.c
- 亲缘进程通信举例
null
- 非亲缘进程通信举例
fifo_read.c、fifo_write.c
消息队列(Message Queue)
- 消息队列就是消息的一个链表,它允许一个或多个进程向它写消息,一个或多个进程从中读消息。具有一定的FIFO的特性,但是可实现消息的随即查询。这些消息存在于内核中,由“队列ID”来标识。
- 消息队列的实现包括创建和打开队列、添加消息、读取消息和控制消息队列这四种操作。
- int msgget (key_t key, int flag)
key:返回新的或已有队列的ID。
flag:通常取值IPC_CREAT。 - int msgsnd (int msqid, struct msgbuf *msgp,
size_t msgsz, int flag)
msqid:消息队列的队列ID;
msgp:消息内容所在的缓冲区;
msgsz:消息的大小;
msgflg:控制消息队列满或者到达系统上限将发生的事情,例如:设置IPC_NOWAIT则消息不发送并返回-1;清除IPC_NOWAIT则发送进程挂起直到消息队列腾出空间后返回。 - int msgrcv (int msqid, struct msgbuf *msgp,
size_t msgsz,long msgtyp, int flag)
msqid:消息队列的引用标识符;
msgp:接收到的消息将要存放的缓冲区;
msgsz:消息的大小,不包含消息类型;
msgtyp:期望接收的消息类型,一般取值0;
msgflg:没有相应消息可供接收时应发生的事情,例如:IPC_NOWAIT。 - int msgctl (int msqid, int cmd, struct msqid_ds *buf)
msqid是消息队列的引用标识符;
cmd是执行命令;
buf是一个缓冲区。
cmd参数指定对于由msqid规定的队列要执行的命令:
IPC_STAT 取此队列的msqid_ds结构,并将其存放在buf指向的结构中。
IPC_SET 按由buf指向的结构中的值,设置与此队列相关的结构中的下列四个字段: msg_perm.uid、msg_perm.gid、msg_perm;mode和msg_qbytes。
IPC_RMID 从系统中删除该消息队列以及仍在该队列上的所有数据。这种删除立即生效。仍在使用这一消息队列的其他进程在它们下一次试图对此队列进行操作时,将出错返回EIDRM。 - 通过消息队列,实现数据接收、发送。
发送消息:msg_sender_example.c
接收消息:msg_receiver_example.c
内存映射
- #include “sys/mman.h”
void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset);
功能:将一个文件或者其它对象映射进内存。
start:映射区的开始地址,一般取NULL。
len:是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。
prot :指定共享内存的访问权限。
flags:映射对象的类型。
fd:即将映射到进程空间的文件描述字,一般由open()返回,也可以指定为-1,此时须指定flags参数中的MAP_ANON。
offset:偏移量,一般为0,表示从文件头开始映射。 - #include “sys/mman.h”
int munmap(void *start, size_t length);
功能:在进程地址空间中解除一个映射关系。
start:映射区的开始地址,一般取mmap()时返回的地址。
Len:映射区的大小。
返回值:成功,返回0;失败,返回-1。 - 内存映射的典型代码
- 目的:使用普通文件提供的内存映射,在任何进程之间实现数据共享。
- 方式一:非亲缘进程通信
fd=open(name, flag, mode);
if(fd<0)
…
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE,
MAP_SHARED , fd , 0); - 方式二:亲缘进程通信
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE,
MAP_ANON|MAP_SHARED , -1 , 0);
- 内存映射举例1
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct
{
char name[4];
int age;
}people;
main(int argc, char** argv)
{
int fd,i;
people *p_map;
char temp;
fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,0777);
lseek(fd,sizeof(people)*5-1,SEEK_SET);
write(fd,"",1);
p_map = (people*) mmap(
NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
MAP_SHARED,fd,0 );
close( fd );
temp = 'a';
for(i=0; i<10; i++)
{
temp += 1;
memcpy( ( *(p_map+i) ).name, &temp,2 );
( *(p_map+i) ).age = 20+i;
}
printf(" initialize over \n ");
sleep(10);
munmap( p_map, sizeof(people)*10 );
printf( "umap ok \n" );
}
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
char name[4];
int age;
}people;
main(int argc, char** argv)
{
int fd,i;
people *p_map;
fd=open( argv[1],O_CREAT|O_RDWR,00777 );
p_map = (people*)mmap(NULL,
sizeof(people)*10,
PROT_READ|PROT_WRITE,
MAP_SHARED,fd,0);
for(i = 0;i<10;i++)
{
printf( "name: %s age %d;\n",
(*(p_map+i)).name,
(*(p_map+i)).age );
}
munmap( p_map,sizeof(people)*10 );
}
- 内存映射举例2
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
char name[4];
int age;
}people;
main(int argc, char** argv)
{
int i;
people *p_map;
char temp;
p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
if(fork() == 0)
{
sleep(2);
for(i = 0;i<5;i++)
printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age);
(*p_map).age = 100;
munmap(p_map,sizeof(people)*10);
exit();
}
temp = 'a';
for(i = 0;i<5;i++)
{
temp += 1;
memcpy((*(p_map+i)).name, &temp,2);
(*(p_map+i)).age=20+i;
}
sleep(5);
printf( "parent read: the first people,s age is %d\n",(*p_map).age );
printf("umap\n");
munmap( p_map,sizeof(people)*10 );
printf( "umap ok\n" );
}
- 结论
- 最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小;
- 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小,但不完全受限于文件大小。
- 文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时脱离了磁盘上文件的影响。
- 所有对mmap()返回地址空间的操作只在内存中有意义,只有在调用了munmap()后,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。
共享内存(Share Memory)
共享内存区域是被多个进程共享的一部分物理内存,是进程间数据共享的一种最快的方法。
共享内存实现步骤
- 创建共享内存,使用shmget函数。
- 原型:int shmget ( key_t key, int size, int shmflg );
key:共享内存的键值。
size:建立共享内存的长度。
shmflg:
IPC_CREAT:如果共享内存不存在,则创建,否则打开。
IPC_EXCL:如果共享内存不存在,则建立,否则报错。 - 返回值:如果成功,返回共享内存段标识符。如果失败,则返回- 1:
- 原型:int shmget ( key_t key, int size, int shmflg );
- 映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
- 原型:void * shmat ( int shmid, char *shmaddr,
int shmflg);
shmid:共享内存标识符;
shmaddr:进程映射地址,通常为null;
shmflg:映射地址空间访问属性,例如SHM_RDONLY; - 返回值:如果成功,则返回共享内存段连接到进程中的地址。如果失败,则返回- 1:
- errno = EINVAL (无效的IPC ID 值或者无效的地址)
ENOMEM (没有足够的内存)
EACCES (存取权限不够)
- 原型:void * shmat ( int shmid, char *shmaddr,
- 解除
- 系统调用:shmdt();
- 调用原型:int shmdt ( char *shmaddr );
- 返回值:如果失败,则返回- 1:
- errno = EINVAL (无效的连接地址)
- 共享内存控制函数
- 函数原型:
int shmctl(int shm_id, int cmd,
struct shmid_ds *buf);
shm_id:共享内存标识符
cmd:将要采取的动作,可以取值IPC_STAT、IPC_SET、IPC_RMID,分别表示把shmid_ds结构中的数据设置为共享内存的当前关联值、按照shmid_ds设置共享内存和删除共享内存段。
buf:保存共享内存的模式状态。
- 函数原型:
- 共享内存举例
- 生产者和消费者
生产者进程:shm_input.c
消费者进程:shm_output.c
- 生产者和消费者
同主机进程间同步机制—信号量(semaphore)
并发程序设计
互斥和同步
PV操作(原语)
- P操作:用于等待
- V操作:用于信号
procedure p(var s:samephore){
s.value=s.value-1;
if (s.value<0) sleep(s.queue);
}
procedure v(var s:samephore){
s.value=s.value+1;
if (s.value<=0) wakeup(s.queue);
}
一个理论性的例子
网络主机间数据交互机制—套接字(socket)
略
补充例子
sigaction_sa_sigaction.c
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void func(int signo, siginfo_t *info, void *p)
{
printf("signo=%d\n",signo);
printf("sender pid=%d\n",info->si_pid);
}
int main(int argc,char *argv[])
{
struct sigaction act, oact;
sigemptyset(&act.sa_mask); /*initial. to empty mask*/
act.sa_flags = SA_SIGINFO;
act.sa_sigaction=func;
sigaction(SIGUSR1, &act, &oact);
while (1)
{
printf("pid is %d Hello world.\n",getpid());
pause();
}
}
kill_example.c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sig_usr(int sig);
int main(int argc,char *argv[])
{
int i = 0;
if(signal(SIGABRT,sig_usr) == SIG_ERR)
printf("Cannot catch SIGUSR1\n");
if (signal(SIGUSR2,sig_usr) == SIG_ERR)
printf("Cannot catch SIGUSR2\n");
while(1) {
// printf("%2d\n", i);
// pause();
/* pause until signal handler
has processed signal */
i++;
}
return 0;
}
void sig_usr(int sig)
{
if (sig == SIGABRT)
printf("Received SIGUSR1\n");
else if (sig == SIGUSR2)
printf("Received SIGUSR2\n");
else
printf("Undeclared signal %d\n", sig);
}
然后就卡住了,Ctrl+C都不管用
sigaction_sigset.c
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int output(sigset_t set)
{
printf("set.val[0]=%x\n",set.__val[0]);
}
void handler(int sig)
{
int i;
sigset_t sysset;
printf("\nin handler sig=%d\n",sig);
sigprocmask(SIG_SETMASK,NULL,&sysset);
output(sysset); //in handler to see the process mask set
printf("return\n");
}
int main(int argc,char *argv[])
{
struct sigaction act;
sigset_t set,sysset,newset;
sigemptyset(&set);
sigemptyset(&newset);
sigaddset(&set,SIGUSR1);
sigaddset(&newset,SIGUSR2);
printf("\nadd SIGUSR1,the value of set:");
output(set);
printf("\nadd SIGUSR2,the value of newset:");
output(newset);
printf("\nafter set proc block set ,and then read to sysset\n");
sigprocmask(SIG_SETMASK,&set,NULL);
sigprocmask(SIG_SETMASK,NULL,&sysset);
printf("system mask is:\n");
output(sysset);
printf("install SIGALRM,and the act.sa_mask is newset(SIGUSR2)\n");
act.sa_handler=handler;
act.sa_flags=0;
act.sa_mask=newset;
sigaction(SIGALRM,&act,NULL);
pause();
printf("after exec ISR\n");
sigemptyset(&sysset);
sigprocmask(SIG_SETMASK,NULL,&sysset);
output(sysset);
}
sigsuspend_test.c
#include<signal.h>
#include<stdlib.h>
#include<errno.h>
#include<stdio.h>
void pr_mask(char *str)
{
sigset_t sigset01;
int errno_save;
errno_save=errno;
if(sigprocmask(0,NULL,&sigset01)<0)
perror("sigprocmask erro!");
printf("%s\n",str);
if(sigismember(&sigset01,SIGINT))
printf("SIGINT\n");
if(sigismember(&sigset01,SIGQUIT))
printf("SIGQUIT\n");
if(sigismember(&sigset01,SIGUSR1))
printf("SIGUSR1\n");
if(sigismember(&sigset01,SIGALRM))
printf("SIGALRM\n");
errno=errno_save;
}
static void sig_int(int signo)
{
printf("signo=%d\n",signo);
pr_mask("\ntest :in sig_int\n");
}
int main(void)
{
sigset_t newmask,oldmask,waitmask;
pr_mask("program start:");
if(signal(SIGINT,sig_int)==SIG_ERR)
perror("signal(SIGINT) error!!\n");
if(signal(SIGUSR1,sig_int)==SIG_ERR)
perror("signal(SIGUSR1) error");
sigemptyset(&waitmask);
sigaddset(&waitmask,SIGUSR1);
sigemptyset(&newmask);
sigaddset(&newmask,SIGINT);
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)
perror("SIG_BLOCK erro!!\n");
pr_mask("in critical region:");
if(sigsuspend(&waitmask)!=-1)
perror("sigsuspend erro!!\n");
pr_mask("after return from sigsuspend:");
if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)
perror("SIG-SETMASK erro!!\n");
while(1);
pr_mask("\nprogram exit:\n");
exit(0);
}