现在Linux使用的进程间通信方式包括:
1、管道(pipe)和有名管道(FIFO)
2、信号(signal)
3、共享内存
4、消息队列
5、信号量
6、套接字(socket)(研究通讯时再讲)
1、管道通信
管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞。
管道包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。
无名管道由pipe()函数创建:int pipe(int filedis[2]);
当一个管道建立时,它会创建两个文件描述符:filedis[0] 用于读管道, filedis[1] 用于写管道。
管道关闭:关闭管道只需将这两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。
[cpp] view plaincopy
#include
#include
#include
#include
int main()
{
int pipe_fd[2];
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}
else
printf("pipe create success\n");
close(pipe_fd[0]);
close(pipe_fd[1]);
}
管道用于不同进程间通信。通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。
注意:必须在系统调用fork( )前调用pipe( ),否则子进程将不会继承文件描述符。
pipe_rw.c
[cpp] view plaincopy
#include
#include
#include
#include
#include
#include
int main()
{
int pipe_fd[2];
pid_t pid;
char buf_r[100];
char* p_wbuf;
int r_num;
memset(buf_r,0,sizeof(buf_r));
/*创建管道*/
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}
/*创建子进程*/
if((pid=fork())==0) //子进程执行序列
{
printf("\n");
close(pipe_fd[1]);//子进程先关闭了管道的写端
sleep(2); /*让父进程先运行,这样父进程先写子进程才有内容读*/
if((r_num=read(pipe_fd[0],buf_r,100))>0)
{
printf("%d numbers read from the pipe is %s\n",r_num,buf_r);
}
close(pipe_fd[0]);
exit(0);
}
else if(pid>0) //父进程执行序列
{
close(pipe_fd[0]); //父进程先关闭了管道的读端
if(write(pipe_fd[1],"Hello",5)!=-1)
printf("parent write1 Hello!\n");
if(write(pipe_fd[1]," Pipe",5)!=-1)
printf("parent write2 Pipe!\n");
close(pipe_fd[1]);
waitpid(pid,NULL,0); /*等待子进程结束*/
exit(0);
}
return 0;
}
命名管道(FIFO)
命名管道和无名管道基本相同,但也有不同点:无名管道只能由父子进程使用;但是通过命名管道,不相关的进程也能交换数据。
1)创建
#include
#include
int mkfifo(const char * pathname, mode_t mode)
参数:
pathname:FIFO文件名
mode:属性一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、write等)都可用于FIFO。
函数说明:
mkfifo ()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode%~umask),因此 umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。当使用open()来打开 FIFO文件时,非阻塞标志O_NONBLOCK旗标会有影响
1、当使用非阻塞标志O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会返回ENXIO 错误代码。
2、没有使用非阻塞标志O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。
返回值
若成功则返回0,否则返回-1,错误原因存于errno中。
错误代码
EACCESS 参数pathname所指定的目录路径无可执行的权限
EEXIST 参数pathname所指定的文件已存在。
ENAMETOOLONG 参数pathname的路径名称太长。
ENOENT 参数pathname包含的目录不存在
ENOSPC 文件系统的剩余空间不足
ENOTDIR 参数pathname路径中的目录存在但却非真正的目录。
EROFS 参数pathname指定的文件存在于只读文件系统内。
fifo_write.c
[cpp] view plaincopy
#include
#include
#include
#include
#include
#include
#include
#define FIFO_SERVER "/tmp/myfifo"
int main(int argc,char** argv)
{
int fd;
char w_buf[100];
int nwrite;
/*创建有名管道*/
if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL|O_RDWR)<0)&&(errno!=EEXIST))
printf("cannot create fifoserver\n");
/*打开管道*/
fd=open(FIFO_SERVER,O_RDWR|O_NONBLOCK,0);
if(fd==-1)
{
perror("open");
exit(1);
}
/*入参检测*/
if(argc==1)
{
printf("Please send something\n");
exit(-1);
}
strcpy(w_buf,argv[1]);
/* 向管道写入数据 */
if((nwrite=write(fd,w_buf,100))==-1)
{
if(errno==EAGAIN)
printf("The FIFO has not been read yet.Please try later\n");
}
else
printf("write %s to the FIFO\n",w_buf);
close(fd); //关闭管道
return 0;
}
fifo_read.c
[cpp] view plaincopy
#include
#include
#include
#include
#include
#include
#include
#define FIFO "/tmp/myfifo"
int main(int argc,char** argv)
{
char buf_r[100];
int fd;
int nread;
printf("Preparing for reading bytes...\n");
memset(buf_r,0,sizeof(buf_r));
/* 打开管道 */
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
if(fd==-1)
{
perror("open");
exit(1);
}
while(1)
{
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100))==-1)
{
if(errno==EAGAIN)
printf("no data yet\n");
}
printf("read %s from FIFO\n",buf_r);
sleep(1);
}
//后面三句话是不会被运行到的,但不会影响程序运行的效果当程序在上面的死循环中执行时收到信号后会马上结束运行而没有执行后面的三句话。这些会在后面的信号处理中讲到,现在不理解没有关系,这个问题留给大家学习了信号处理之后来解决。
close(fd); //关闭管道
pause(); /*暂停,等待信号*/
unlink(FIFO); //删除文件
}
2、信号通信
信号(signal)机制是Unix系统中最为古老的进程间通信机制,很多条件可以产生一个信号:
1)、当用户按某些按键时,产生信号。
2)、硬件异常产生信号:除数为0、无效的存储访问等等。这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,例
如,内核对正访问一个无效存储区的进程产生一个SIGSEGV信号。
3)、进程用kill函数将信号发送给另一个进程。
4)、用户可用kill命令将信号发送给其他进程。
信号类型
Signal
Description
SIGABRT
由调用abort函数产生,进程非正常退出
SIGALRM
用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
SIGBUS
某种特定的硬件异常,通常由内存访问引起
SIGCANCEL
由Solaris Thread Library内部使用,通常不会使用
SIGCHLD
进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
SIGCONT
当被stop的进程恢复运行的时候,自动发送
SIGEMT
和实现相关的硬件异常
SIGFPE
数学相关的异常,如被0除,浮点溢出,等等
SIGFREEZE
Solaris专用,Hiberate或者Suspended时候发送
SIGHUP
发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送
SIGILL
非法指令异常
SIGINFO
BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
SIGINT
由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
SIGIO
异步IO事件
SIGIOT
实现相关的硬件异常,一般对应SIGABRT
SIGKILL
无法处理和忽略。中止某个进程
SIGLWP
由Solaris Thread Libray内部使用
SIGPIPE
在reader中止之后写Pipe的时候发送
SIGPOLL
当某个事件发送给Pollable Device的时候发送
SIGPROF
Setitimer指定的Profiling Interval Timer所产生
SIGPWR
和系统相关。和UPS相关。
SIGQUIT
输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
SIGSEGV
非法内存访问
SIGSTKFLT
Linux专用,数学协处理器的栈异常
SIGSTOP
中止进程。无法处理和忽略。
SIGSYS
非法系统调用
SIGTERM
请求中止进程,kill命令缺省发送
SIGTHAW
Solaris专用,从Suspend恢复时候发送
SIGTRAP
实现相关的硬件异常。一般是调试异常
SIGTSTP
Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
SIGTTIN
当Background Group的进程尝试读取Terminal的时候发送
SIGTTOU
当Background Group的进程尝试写Terminal的时候发送
SIGURG
当out-of-band data接收的时候可能发送
SIGUSR1
用户自定义signal 1
SIGUSR2
用户自定义signal 2
SIGVTALRM
setitimer函数设置的Virtual Interval Timer超时的时候
SIGWAITING
Solaris Thread Library内部实现专用
SIGWINCH
当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
SIGXCPU
当CPU时间限制超时的时候
SIGXFSZ
进程超过文件大小限制
SIGXRES
Solaris专用,进程超过资源限制的时候发送
下面是几种常见的信号:
§ SIGHUP: 从终端上发出的结束信号
§ SIGINT: 来自键盘的中断信号(Ctrl-C)
§ SIGKILL:该信号结束接收信号的进程
§ SIGTERM:kill 命令发出的信号
§ SIGCHLD:标识子进程停止或结束的信号
§ SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号
当某信号出现时,将按照下列三种方式中的一种进行处理:
1)、忽略此信号
大多数信号都按照这种方式进行处理,但有两种信号却决不能被忽略。它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供了一种终止或停止进程的方法。
2)、执行用户希望的动作
通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理。
3)、执行系统默认动作
对大多数信号的系统默认动作是终止该进程。
发送信号的主要函数有 kill和raise。
区别:
Kill既可以向自身发送信号,也可以向其他进程发送信号。与kill函数不同的是,raise函数是向进程自身发送信号。
#include
#include
int kill(pid_t pid, int signo)
函数说明
kill()可以用来送参数sig指定的信号给参数pid指定的进程。参数pid有几种情况:
pid>0 将信号传给进程识别码为pid 的进程。
pid=0 将信号传给和当前进程相同进程组的所有进程
pid=-1 将信号广播传送给系统内所有的进程
pid<0 将信号传给进程组识别码为pid绝对值的所有进程
返回值 执行成功则返回0,如果有错误则返回-1。
错误代码:
EINVAL 参数sig 不合法
ESRCH 参数pid 所指定的进程或进程组不存在
EPERM 权限不够无法传送信号给指定进程
#include
#include
int raise(int signo)
函数说明
raise()将参数sig指定的信号发送给自身进程。
参数说明
sig 系统信号
函数返回
成功:0
失败:-1
Alarm
使用alarm函数可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生SIGALRM信号。如果不捕捉此信号,则默认动作是终止该进程。
#include
unsigned int alarm(unsigned int seconds)
Seconds:
经过了指定的seconds秒后会产生信号SIGALRM。
返回值:
成功:如果调用此alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
出错:-1
每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,以前登记的闹钟时间则被新值代换。
如果有以前登记的尚未超过的闹钟时间,而这次seconds值是0,则表示取消以前的闹钟。
Pause
pause函数使调用进程挂起直至捕捉到一个信号。
#include
int pause(void)
函数说明:只有执行了一个信号处理函数后,挂起才结束。pause()会令目前的进程暂停(进入睡眠状态),直到被信号(signal)所中断。
返回值: 只返回-1。
signal
#include
void (*signal (int signo, void (*func)(int)))(int)
如何理解?
typedef void (*sighandler_t)(int)
sighandler_t signal(int signum, sighandler_t handler))
Func可能的值是:
1、SIG_IGN:忽略此信号
2、SIG_DFL: 按系统默认方式处理
3、信号处理函数名:使用该函数处理
[cpp] view plaincopy
#include
#include
#include
/*自定义信号处理函数*/
void my_func(int sign_no)
{
if(sign_no==SIGBUS)
printf("I have get SIGBUS\n");
}
int main()
{
printf("Waiting for signal SIGBUS \n ");
/*注册信号处理函数*/
signal(SIGBUS,my_func);
pause();//将进程挂起直到捕捉到信号为止
exit(0);
}
3、共享内存
共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
共享内存实现分为两个步骤:
一、创建共享内存,使用shmget函数。
二、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成。
shmget函数原型
shmget(得到一个共享内存标识符或创建一个共享内存对象)
所需头文件
#include
#include
函数说明
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
函数原型
int shmget(key_t key, size_t size, int shmflg)
函数传入值
key
0(IPC_PRIVATE):会建立新共享内存对象
大于0的32位整数:视参数shmflg来确定操作。通常要求此值来源于ftok返回的IPC键值
size
大于0的整数:新建的共享内存大小,以字节为单位
0:只获取共享内存时指定为0
shmflg
0:取共享内存标识符,若不存在则函数会报错
IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错
函数返回值
成功:返回共享内存的标识符
出错:-1,错误原因存于error中
附加说明
上述shmflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限
错误代码
EINVAL:参数size小于SHMMIN或大于SHMMAX
EEXIST:预建立key所指的共享内存,但已经存在
EIDRM:参数key所指的共享内存已经删除
ENOSPC:超过了系统允许建立的共享内存的最大值(SHMALL)
ENOENT:参数key所指的共享内存不存在,而参数shmflg未设IPC_CREAT位
EACCES:没有权限
ENOMEM:核心内存不足
shmat
shmat(把共享内存区对象映射到调用进程的地址空间)
所需头文件
#include
#include
函数说明
连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg)
函数传入值
shmid
共享内存标识符
shmaddr
指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
shmflg
SHM_RDONLY:为只读模式,其他为读写模式
函数返回值
成功:附加好的共享内存地址
出错:-1,错误原因存于error中
附加说明
fork后子进程继承已连接的共享内存地址。exec后该子进程与已连接的共享内存地址自动脱离(detach)。进程结束后,已连接的共享内存地址会自动脱离(detach)
错误代码
EACCES:无权限以指定方式连接共享内存
EINVAL:无效的参数shmid或shmaddr
ENOMEM:核心内存不足
shmdt函数原型
shmdt(断开共享内存连接)
所需头文件
#include
#include
函数说明
与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
函数原型
int shmdt(const void *shmaddr)
函数传入值
shmaddr:连接的共享内存的起始地址
函数返回值
成功:0
出错:-1,错误原因存于error中
附加说明
本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程
错误代码
EINVAL:无效的参数shmaddr
shmctl函数原型
shmctl(共享内存管理)
所需头文件
#include
#include
函数说明
完成对共享内存的控制
函数原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
函数传入值
shmid
共享内存标识符
cmd
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
IPC_RMID:删除这片共享内存
buf
共享内存管理结构体。具体说明参见共享内存内核结构定义部分
函数返回值
成功:0
出错:-1,错误原因存于error中
错误代码
EACCESS:参数cmd为IPC_STAT,确无权限读取该共享内存
EFAULT:参数buf指向无效的内存地址
EIDRM:标识符为shmid的共享内存已被删除
EINVAL:无效的参数cmd或shmid
EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行
shm_com.h
[cpp] view plaincopy
#define TEXT_SZ 2048
struct shared_use_st
{
int written_by_you;
char some_text[TEXT_SZ];
};
shm1.c
[cpp] view plaincopy
#include
#include
#include
#include
#include
#include
#include
#include "shm_com.h"
int main(void)
{
int running=1;
void *shared_memory=(void *)0;
struct shared_use_st *shared_stuff;
int shmid;
/*创建共享内存*/
shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid==-1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
/*映射共享内存*/
shared_memory=shmat(shmid,(void *)0,0);
if(shared_memory==(void *)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n",(int)shared_memory);
/*让结构体指针指向这块共享内存*/
shared_stuff=(struct shared_use_st *)shared_memory;
/*控制读写顺序*/
shared_stuff->written_by_you=0;
/*循环的从共享内存中读数据,直到读到“end”为止*/
while(running)
{
if(shared_stuff->written_by_you)
{
printf("You wrote:%s",shared_stuff->some_text);
sleep(1); //读进程睡一秒,同时会导致写进程睡一秒,这样做到读了之后再写
shared_stuff->written_by_you=0;
if(strncmp(shared_stuff->some_text,"end",3)==0)
{
running=0; //结束循环
}
}
}
/*删除共享内存*/
if(shmdt(shared_memory)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
shm2.c
[cpp] view plaincopy
#include
#include
#include
#include
#include
#include
#include
#include "shm_com.h"
int main(void)
{
int running=1;
void *shared_memory=(void *)0;
struct shared_use_st *shared_stuff;
char buffer[BUFSIZ];
int shmid;
/*创建共享内存*/
shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid==-1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
/*映射共享内存*/
shared_memory=shmat(shmid,(void *)0,0);
if(shared_memory==(void *)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n",(int)shared_memory);
/*让结构体指针指向这块共享内存*/
shared_stuff=(struct shared_use_st *)shared_memory;
/*循环的向共享内存中写数据,直到写入的为“end”为止*/
while(running)
{
while(shared_stuff->written_by_you==1)
{
sleep(1);//等到读进程读完之后再写
printf("waiting for client...\n");
}
printf("Ener some text:");
fgets(buffer,BUFSIZ,stdin);
strncpy(shared_stuff->some_text,buffer,TEXT_SZ);
shared_stuff->written_by_you=1;
if(strncmp(buffer,"end",3)==0)
{
running=0; //结束循环
}
}
/*删除共享内存*/
if(shmdt(shared_memory)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
阅读(521) | 评论(0) | 转发(0) |