1、管道(pipe)和有名管道(FIFO)
2、信号(signal)
3、共享内存
4、消息队列
6、套接字(socket)(研究通讯时再讲)
1、管道通信
管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞。
管道包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。
无名管道由pipe()函数创建:int pipe(int filedis[2]);
当一个管道建立时,它会创建两个文件描述符:filedis[0] 用于读管道, filedis[1] 用于写管道。
管道关闭:关闭管道只需将这两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
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( ),否则子进程将不会继承文件描述符。
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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 <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname, mode_t mode)
参数:
pathname:FIFO文件名
mode:属性一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、write等)都可用于FIFO。
函数说明:
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signo)
函数说明
返回值 执行成功则返回0,如果有错误则返回-1。
#include <signal.h>
int raise(int signo)
使用alarm函数可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生SIGALRM信号。如果不捕捉此信号,则默认动作是终止该进程。
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
Seconds:
经过了指定的seconds秒后会产生信号SIGALRM。
返回值:
每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,以前登记的闹钟时间则被新值代换。
如果有以前登记的尚未超过的闹钟时间,而这次seconds值是0,则表示取消以前的闹钟。
Pause
pause函数使调用进程挂起直至捕捉到一个信号。
#include <unistd.h>
int pause(void)
函数说明:只有执行了一个信号处理函数后,挂起才结束。pause()会令目前的进程暂停(进入睡眠状态),直到被信号(signal)所中断。
返回值: 只返回-1。
signal
#include <signal.h>
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、信号处理函数名:使用该函数处理
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
/*自定义信号处理函数*/
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 <sys/ipc.h>
#include <sys/shm.h>
| |
函数说明
|
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
| |
函数原型
|
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(把共享内存区对象映射到调用进程的地址空间)
| ||
所需头文件
|
#include <sys/types.h>
#include <sys/shm.h>
| |
函数说明
|
连接共享内存标识符为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(断开共享内存连接)
| |
所需头文件
|
#include <sys/types.h>
#include <sys/shm.h>
|
函数说明
|
与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
|
函数原型
|
int shmdt(const void *shmaddr)
|
函数传入值
|
shmaddr:连接的共享内存的起始地址
|
函数返回值
|
成功:0
|
出错:-1,错误原因存于error中
| |
附加说明
|
本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程
|
错误代码
|
EINVAL:无效的参数shmaddr
|
shmctl(共享内存管理)
| ||
所需头文件
|
#include <sys/types.h>
#include <sys/shm.h>
| |
函数说明
|
完成对共享内存的控制
| |
函数原型
|
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
#define TEXT_SZ 2048
struct shared_use_st
{
int written_by_you;
char some_text[TEXT_SZ];
};
shm1.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#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
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#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);
}