一、概述
目的:
1.数据间的通信。一个进程需要将他的数据传递给其他进程。
2.资源的共享。多个进程间进行数据的共享。
3.事件的通知。一个进程需要想另一个货一组进程发送消息,通知他们发送了某种事件。
4.进行控制。有些进程希望完全控制另一个进程的执行(如debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时的指导它的状态改变。
发展:
1.UNIX进程间通信
2.基于Sysrem V进程间通信
3.POSIX(Portable Operating System Interfance
可移植操作系统接口)进程间通信
进程通信的分类:
1.管道(pipe)和有名管道(FIFO)
2.信号(signal)
3.消息队列
4.共享内存
5.信号量
6.套接字(socket)
二、管道通信
特点:管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的末尾写入数据,另一个进程(读进程)在管道的的头部读出数据。数据被一个进程从管道中读出后,将被从管道中删除,其他进程将不能再对该数据进行读取。
分类:
无名管道、有名管道。无名管道使用与父进程与子进程间的通信;有名管道可以使用与任意两个进程间的通信。
无名管道由pipe()函数创建:
无名管道:
int pipe(int filedis[2]);
当一个管道建立时,它会创建两个文件描述符:filedis[0]用于读管道,filedis[1]用于写管道。
管道用于不同进程间的通信,通常先创建一个管道,再通过fork()函数创建一个子进程,该子进程会继承父进程所创建的管道。(必须在系统调用fork()前调用pipe(),否则子进程不会继承父文件描述符)
实例一(父进程与子进程间的通信):pipe_rw.c
#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 number 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],"Helio ",6)!=-1)
printf("write1 Hello success!\n ");
if(write(pipe_fd[1],"pipe",5)!=-1)
printf("\nwrite2 pipe success!\n ");
close(pipe_fd[1]);
sleep(3);
waitpid(pid,NULL,0);
exit(0);
}
}
运行结果:
有名管道:
函数原型:int mkfifo(const char
*pathname,mode_t mode)
pathname:FIFO文件名
mode:属性(创建、读写等)
一旦创建了一个FIFO,就可以使用open打开它,一般的文件访问函数(close、read、write等)都是用于FIFO。
头文件:#inculde、#include
非阻塞标志:
打开FIFO时,被阻塞标志(O_NONBLOCK)将对后面的读写产生如下的影响:
1.没有使用O_NONBLOCK:访问要求无法满足时进行阻塞。如试图读取空的FIFO时,将导致进行阻塞。
2.使用O_NONBLOCK:访问要求无法满足时不进行阻塞,立刻出错返回,errno是ENXIO.
实例二(使用有名管道对FIFO进行读写):fifo_write.c fifo_read.c
fifo_read.c
#include
#include
#include
#include
#include
#include
#include
#include
#define FIFO "/tmp/myfifo"
main(int argc,char **argv)
{
char buf_r[100];
int fd;
int nread;
//创建管道
if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
printf("can't create fifoserver!\n");
printf("preparing fof 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);
}
pause();//暂停,等待信号
unlink(FIFO);
}
fifo_write.c
#include
#include
#include
#include
#include
#include
#include
#include
#define FIFO_SERVER "/tmp/myfifo"
main(int argc,char** argv)
{
int fd;
char w_buf[100];
int nwrite;
//打开管道
fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
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);
}
运行结果:
三、信号通信
信号(signal)机制是unix系统中作古老的通信机制,很多条件可以产生信号:
1.当用户按下某个按键时,产生信号;
2.硬件的异常产生心海:除数为0、无效的存储访问等。这些情况通常有硬件检测到,将其通知内核,然后内核产生适当的信号通知进程。
3.进程用kill函数将信号发送给另一个进程。
4.用户可用kill命令将信号发送给其他进程。
信号的类型:
1)SIGHUP 2)SIGINT 3)SIGQUIT 4)SIGILL 5)SIGTRAP 6)SIGIOT 7)SIGBUS 8)SIGFPE
9)SIGKILL 10)SIGUSR1 11)SIGSEGV 12)SIGSR2 13)SIGPIPE 14)SIGALRM 15)SIGTERM
16)SIGCHLD 17)SIGCONT 18)SIGSTOP 19)SIGTSTP 20)SIGTTIN 21)SIGTTOU 22)SIGURG
23)SIGXCPU 24)SIGXFSZ 25)SIGVTALRM 26)SIGPROF 27)SIGWINCH 28)SIGIO 29)SIGPWR
下面几种是常见的信号类型:
SIGHUP:从终端上发送的结束信号
SIGINT:来自键盘的中断信号(ctrl+C)
SIGKILL:该信号结束接受信号的进程
SIGCHLD:标识子进程停止或结束的信号
SIGSTOP:来自键盘(ctrl+z)或调试程序的停止执行信号
信号的处理:
当某种信号出现时,将按照下列三种方式中的一种进行处理:
1.忽略此信号 大多数信号都按照这种方式进行处理,但是有两种信号却决不能忽略,他们是:SIGKILL和SIGSTOP。不能被忽略的原因是:他们向超级用户提供了一种终止或停止进程的方法。
2.执行用户希望的动作 通知内核在某种信号发生时,调用一个用户的函数,在用户函数中,执行用户希望的处理。
3.执行系统默认的动作 多大多数信号的系统默认动作时终止该进程。
信号的发送:
发送型号的函数有kill和raise。他们的区别为:
kill既可以向自身发送信号,也可以向其他进程发送信号。raise函数只能向进程自身发送信号。
头文件和函数原型:
#include
#include
int kill(pid_t pid,int signo)
int raise(int signo)
kill的pid参数有四种不同的情况:
1.pid>0 将信号发送给进程ID为pid的进程;
2.pid==0 将进程发送给同组的进程;
3.pid<0 将信号发送给其他进程组ID等于pid绝对值的进程;
4.pid==-1 将信号发送给所有的进程。
ALARM函数:
使用alarm函数可以设置一个时间(闹钟时间),当达到了所设定的时间,产生SIGALARM信号。如果不捕捉此信号,则默认动作时终止该进程。
#include
unsigned int alarm(unsigned int seconds)
seconds:经过了指定的seconds秒后会产生信号SIGALARM.
每个进程只能有一个闹钟时间。如果在调用alarm,时以前为改进从设置过闹钟时间,而且他还没有超时,以前登记的闹钟时间将被新值所替换。如果新登记的时间为0,表示取消以前的闹钟设置。
Pause函数:
pause函数使调用进程挂起直至捕捉到一个信号。
#include
int pause(void)
只有执行了一个信号处理函数后,挂起才结束。
signal的函数原型:
#include
void (*signal(int
signo,void(*func)(int)))(int)
理解这个函数从下面2个函数来理解:
typedef void(*sighandler_t)(int)
sighandler_t signal(int signum,sighandler_t
handler)
Func的值可能是:
1.SIG_IGN:忽略此信号
2.SIG_DFL:按系统默认方式处理
3.信号处理函数名:使用指定的函数处理
函数实例三:mysignal.c
#include
#include
#include
void my_func(int sign_no)
{
if(sign_no==SIGINT)
printf("I have get SIGINT!\n");
else if(sign_no==SIGQUIT)
printf("I have get
SIGQUIT!\n"); }
int main()
{
printf("Waiting for signal SIGINT or
SIGQUIT:\n");
//注册信号处理函数
signal(SIGINT,my_func);
signal(SIGQUIT,my_func);
pause();
exit(0);
}
执行结果:
先执行该程序,然后再另一个终端中使用ps
aux进行进程号的查询,找到所执行程序的进程号,再执行命令:kill -s SIGQUIT 进程号
四、共享内存
共享内存是被多个进程共享的一部分内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就立即看到了内存的数据。
共享内存的实现分为两个步骤:
1.创建共享内存 使用shmget函数;
2.映射共享内存 将这段创建的共享内存映射到据图的进程空间去,使用shmat函数。
函数原型:
创建: int shmget(key_t hey,int
size,int shmflg)
key标识共享内存的键值:0/IPC_PRIVATE.当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共性内存,如果key的取值为0,而参数shmflg中又设置IPC_PRIVATE这个标志,则同样会创建一快新的共享内存。
返回值:如果成功,返回共享内存的标示符;如果不成功,返回-1.
映射:int shmat(int shmid,char
*shmaddr,int flag)
参数:1.shmid:shmget函数返回的共享内存的标示符;
2.flag:决定以什么样的方式来确定映射的地址(通常为0:让系统自动分配)
返回值:成功,则返回共享内存映射到进程中的地址;失败,返回-1.
脱离:int shmdt(char *shmaddr)
当一个进程不再需要共享内存时,需要把它从进程地址空间中脱离。
函数实例四:sharemem.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PERM S_IRUSR|S_IWUSR
int main(int argc,char **argv)
{
int shmid;
char *p_addr,*c_addr;
pid_t pid;
if(argc!=2)
{
fprintf(stderr,"Usage:%s\n\a",argv[0]);
exit(1); } //创建共享内存
if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1)
{
fprintf(stderr,"Create Share
Memory Error:",strerror(errno)); exit(1);
}
//创建子进程
//pid=fork();
if(fork())
{
p_addr=shmat(shmid,0,0);
memset(p_addr,'\0',1024);
strncpy(p_addr,argv[1],1024);
wait(NULL);//释放资源,不关心终止状态
exit(0); }
else
{
sleep(1);
c_addr=shmat(shmid,0,0);
printf("Client
get %s\n",c_addr);
exit(0); }
}
执行结果: