进程间通信

1.管道通信
1.1 什么是管道
管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已满时,进程再试图向管道写入数据,进程将阻塞。
管道包括无名管道和有名管道两种,前者用于父进场和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。
1.2 无名管道的创建
int pipe(int filedis[2])
当一个管道建立时,它会创建两个文件描述符:filedis[0]用于读管道,filedis[1]用于写管道。
1.3 关闭管道
关闭管道只需将两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。
1.4 管道读写
管道用于不同进程间通信。通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。注意,必须在系统调用fork()前调用pipe(),否则子进程将不会继承文件描述符。

pipe_rw.c
#include <unistd.h>
#include
<sys/types.h>
#include
<errno.h>
#include
<stdio.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]);
sleep(
3);
waitpid(pid,NULL,
0); /*等待子进程结束*/
exit(
0);
}
return 0;
}
1.5 命名管道(FIFO)的创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)

一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close/read/write等)都可用于FIFO。
1.6 命名管道(FIFO)的操作
当打开FIFO时,非阻塞标志(O_NONBLOCK)将对以后的读写产生如下影响:
如果没有使用O_NONBLOCK,那么访问要求无法满足时进程将阻塞。如果使用O_NONBLOCK,那么访问要求无法满足时不阻塞,立刻出错返回,errono时ENXIO。
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"

main(
int argc,char** argv)
{
char buf_r[100];
int fd;
int nread;

/* 创建管道 */
if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
printf(
"cannot create fifoserver\n");

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);
}
pause();
/*暂停,等待信号*/
unlink(FIFO);
//删除文件
}
fifo_write.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_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);
}
2.信号通讯
2.1 信号发送
发送信号的主要函数有kill和raise。两者的区别是:kill既可以向自身发送信号,也可以向其他进程发送信号;而raise函数是向进程自身发送信号。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signo)
int raise(int signo)

kill的pid参数有四种不同的情况:
pid>0:将信号发送给进程ID为pid的进程。
pid==0:将信号发送给同组的进程。
pid<0:将信号发送给其进程组ID等于pid绝对值的进程。
pid==-1:将信号发送给所有进程。
2.2 alarm函数
使用alarm函数可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生SIGALRM信号。如果不捕捉此信号,则默认动作是终止该进程。
#include <unistd.h>
unsigned int alarm(unsigned int seconds)

seconds:经过了指定的seconds秒后会产生信号SIGALRM。
每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而它还没有超时,以前登记的闹钟时间则被新值代替。如果有以前登记的尚未超过的闹钟时间,而这次的seconds值是0,则表示取消以前的闹钟。
2.3 pause函数
pause函数使调用进程挂起直至捕捉到一个信号。
#include <unistd.h>
int pause(void)

只有执行了一个信号处理函数后,挂起才结束。
2.4 信号的处理
当系统捕捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式处理。
信号处理的主要方法有两种,一种是使用简单的signal函数,另一种是使用信号集函数组。
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int)

func可能的值是:
SIG_IGN:忽略此信号;SIG_DFL:按系统默认方式处理;信号处理函数名:使用该函数处理。
mysignal.c
#include <signal.h>
#include
<stdio.h>
#include
<stdlib.h>

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);
}
3.共享内存通信
共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
共享内存实现分为两个步骤:
一、创建共享内存,使用shmget函数。
二、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
3.1 创建共享内存
int shmget(key_t key, int size, int shmflg)
key标识共享内存的键值:0/IPC_PRIVATE。当key值为IPC_PRIVATE时函数shemget将创建一块新的共享内存;如果key的值为0,而参数shmflg中又设置IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。
如果成功,返回共享内存标识符;如果失败,返回-1。
3.2 映射共享内存
int shmat(int shmid, char *shmaddr, int flag)
参数说明:
shmid:shmget函数返回的共享内存存储标识符
flag:决定以什么方式来确定映射的地址(通常为0)
返回值:
如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回-1。
3.3 脱离共享内存
当一个进程不再需要共享内存时,需要把它从进场地址空间中脱离。
int shmdt(char *shmaddr)
shmem.c
#include <stdlib.h>
#include
<stdio.h>
#include
<string.h>
#include
<errno.h>
#include
<unistd.h>
#include
<sys/stat.h>
#include
<sys/types.h>
#include
<sys/ipc.h>
#include
<sys/shm.h>

#define PERM S_IRUSR|S_IWUSR
/* 共享内存 */

int main(int argc,char **argv)
{
int shmid;
char *p_addr,*c_addr;

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:%s\n\a",strerror(errno));
exit(
1);
}

/* 创建子进程 */
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); // 暂停1秒
c_addr=shmat(shmid,0,0);
printf(
"Client get %s\n",c_addr);
exit(
0);
}
}
4.消息队列
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式。进程可以向队列中按照一定的规测添加新消息;另一些进程则可以从消息队列中读出消息。
目前主要有两种类型的消息队列:POSIX消息队列和系统V消息队列,系统V消息队列目前被大量使用。
系统V消息队列是随内核持续的,只有在内核重启或者人工删除时该消息队列才会被删除。消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,要获得一个消息队列的描述字,必须提供该消息队列的键值。
4.1 获取键值
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(char *pathname, char proj)

功能:返回文件名对应的键值。
参数说明:
pathname:文件名
proj:项目名(不为0即可)
4.2 打开/创建消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int smgflg)

参数说明:
key:键值,由ftok获得
msgflg:标志位
返回值:与键值key相对应的消息队列描述字。
msgflg的取值如下:
IPC_CREATE:创建新的消息队列。
IPC_EXCL:与IPC_CREATE一同使用,表示如果要创建的消息队列已经存在,则返回错误。
IPC_NOWAIT:读写消息队列要求无法得到满足时,不阻塞。
在以下两种情况下,将创建一个新的消息队列:
如果没有与键值key相对应的消息队列,并且msgflg中包含了IPC_CREATE标志位;key参数为IPC_PRIVATE。
4.3 发送消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg)

功能:向消息队列中发送一条消息。
参数说明:
msqid:已打开的消息队列id
msgp:存放消息的结构
msgsz:消息数据长度
msgflg:发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。
消息格式:
struct msgbuf
{
    long mtype; //消息类型 >0
    char mtext[1]; //消息数据的首地址
};

4.4 接收消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg)

功能:从msqid代表的
消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功地读取了一条消息以后,队列中的这条消息将被删除。
msg.c
#include <sys/types.h>
#include
<sys/msg.h>
#include
<unistd.h>

struct msg_buf
{
int mtype;
char data[255];
};

int main()
{
key_t key;
int msgid;
int ret;
struct msg_buf msgbuf;

key
=ftok("/tmp/2",'a');
printf(
"key =[%x]\n",key);
msgid
=msgget(key,IPC_CREAT|0666); /*通过文件对应*/

if(msgid==-1)
{
printf(
"create error\n");
return -1;
}

msgbuf.mtype
= getpid();
strcpy(msgbuf.data,
"test haha");
ret
=msgsnd(msgid,&msgbuf,sizeof(msgbuf.data),IPC_NOWAIT);
if(ret==-1)
{
printf(
"send message err\n");
return -1;
}

memset(
&msgbuf,0,sizeof(msgbuf));
ret
=msgrcv(msgid,&msgbuf,sizeof(msgbuf.data),getpid(),IPC_NOWAIT);
if(ret==-1)
{
printf(
"recv message err\n");
return -1;
}
printf(
"recv msg =[%s]\n",msgbuf.data);

}
5.信号量
信号量与其他进程间通信方式不大相同,主要用途是保护临界资源。进程根据它判定是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步。
5.1 创建/打开信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)

参数说明:
key:键值,由ftok获得。
nsems:指定打开或者创建的信号量集合中将包含信号量的数目。
semflg:标识,同消息队列。
5.2 操作信号量
int semop(int semid, struct sembuf *sops, unsigned nsops)
功能:对信号量进行控制。
semid:信号量集的ID。
sops:一个操作数组,表明要进行什么操作。
nsops:sops所指向的数组的元素个数。
操作数组的结构:
struct sembuf {
unsigned short sem_num;
short sem_op;
short sem_flg;
};

sem_num:要操作的信号量在信号集中的编号,第一个信号的编号是0。
sem_op:如果其值为正数,该值会加到现有的信号量值中,通常用于释放信号量;如果其值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值,通常用于获取信号量;如果其值为0,则操作将暂时阻塞,直到信号的值变为0。
sem_flg:信号操作标志,可能的选择有两种:IPC_NOWAIT,表示对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息;IPC_UNDO,表示程序结束时(不论正常或不正常)释放信号量,这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

posted @ 2010-07-15 20:41  MikeLin  阅读(354)  评论(0编辑  收藏  举报