进程间通信是指不同进程之间交换或传播信息。进程间通信的方式通常有管道通信、消息队列、信号量、共享存储、Socket、Stream等,其中Socket和Stream支持不同主机上的两个进程间的通信。
管道通信创建会在内核开辟一段空间,父进程可以往里面写数据,子进程从里面读取数据;子进程往里面写数据,父进程从里面读取数据。
#include
int pipe(int fd[2])
当开辟管道成功时返回0,失败时则返回-1
当一个管道创建时会创建两个文件描述符,fd[0]为读,fd[1]为写
注意:管道中国若没有数据,但是调用read取读取数据,则会发生阻塞,阻塞在read
若要数据流从父进程流向子进程,则要关闭父进程的读端fd[0]与子进程的写端fd[1],反之,则可以使数据流从子进程流向父进程。
数据流从父进程流向子进程 数据流从子进程流向父进程#include
int mkfifo(const char *pathname, mode_t mode);
成功返回0,出错返回-1
命名管道的数据通信示例:
当open一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
write代码:
#include
#include
#include
#include
#include
#include
//int mkfifo(const char *pathname, mode_t mode);
//int open(const char *pathname, int flags);
//int open(const char *pathname, int flags, mode_t mode);
int main()
{
int cnt = 0;
char *str="message from fifo";
int fd =open("/home/CLC/FILE",O_WRONLY);
printf("write open success\n");
while(1)
{
write(fd, str,strlen(str));
sleep(1);
if(cnt==5)
{
break;
}
}
close(fd);
return 0;
}
read代码:
#include
#include
#include
#include
#include
#include
int main()
{
char buf[30] ={0};
int nread=0;
if((mkfifo("/home/CLC/FILE",0600)==-1) && errno != EEXIST)
{
printf("mkfifo failed\n");
perror("why");
}
int fd =open("/home/CLC/FILE",O_RDONLY);
printf("open successed\n");
while(1)
{
nread =read(fd,buf,30);
printf("read %d byte from fifo,context is %s\n",nread,buf);
}
close();
return 0;
}
以上代码的运行结果是:若管道中还未write数据,read方先从管道中读取,则read会阻塞,知道使用write向管道中写入数据,read才继续执行。
消息队列是消息的链表,存放于内核中。一个消息队列都有一个ID进行标识。
#include
在以下两种情况下,msgget将创建一个新的消息队列:
函数msgrcv在读取消息队列时,type参数有以下几种情况
type值非0时用于以先进先出次序读消息,也可以把type看作优先级的权值。
接收端:
#include
#include
#include
#include
#include
//int msgget(key_t key, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf readbuf;
struct msgbuf sendbuf={988,"thank for attatch"};
int msgid=msgget(0x1234,IPC_CREAT|0600);
if(msgid == -1)
{
printf("creat que failed\n");
}
msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),888,0);
printf("read from que %s\n",readbuf.mtext);
msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
return 0;
}
发送端:
#include
#include
#include
#include
#include
//int msgget(key_t key, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf readbuf;
struct msgbuf sendbuf={988,"thank for attatch"};
int msgid=msgget(0x1234,IPC_CREAT|0600);
if(msgid == -1)
{
printf("creat que failed\n");
}
msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),888,0);
printf("read from que %s\n",readbuf.mtext);
msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
return 0;
}
前面的示例中并没有获取key值,而是直接使用整型数将key值写死的,这种方式自然不太高级,但原理是一模一样的。
系统建立IPC通信时必须指定一个ID。通常情况下,该ID可以通过ftok函数的到
所需头文件:
#include
#include
函数原型:
key_t ftok( const char * fname, int id );
参数:
fname:路径名/文件名
id:子序号,虽然是int型,但是只使用8bits(1-255)
发送端:
#include
#include
#include
#include
#include
//int msgget(key_t key, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf sendbuf={888,"this is message from que"};
struct msgbuf readbuf ;
key_t key;
key=ftok(".",123);
int msgid=msgget(key,IPC_CREAT|0600);
if(msgid == -1)
{
printf("creat que failed\n");
}
msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),988,0);
printf("return from rcv %s\n",readbuf.mtext);
return 0;
}
接收端:
#include
#include
#include
#include
#include
//int msgget(key_t key, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf readbuf;
struct msgbuf sendbuf={988,"thank for attatch"};
key_t key;
key=ftok(".",123);
int msgid=msgget(key,IPC_CREAT|0600);
if(msgid == -1)
{
printf("creat que failed\n");
}
msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),888,0);
printf("read from que %s\n",readbuf.mtext);
msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
return 0;
}
结果:
以上方法中两个进程之间使用消息队列通信,如果内核中没有这个队列,就会直接创建这个队列,长此以往,内核中存放大量的队列,所以用完之后尽量把队列移除。
发送端:
#include
#include
#include
#include
#include
//int msgget(key_t key, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf sendbuf={888,"this is message from que"};
struct msgbuf readbuf ;
key_t key;
key=ftok(".",123);
int msgid=msgget(key,IPC_CREAT|0600);
if(msgid == -1)
{
printf("creat que failed\n");
}
msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),988,0);
printf("return from rcv %s\n",readbuf.mtext);
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
接收端:
#include
#include
#include
#include
#include
//int msgget(key_t key, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
struct msgbuf readbuf;
struct msgbuf sendbuf={988,"thank for attatch"};
key_t key;
key=ftok(".",123);
int msgid=msgget(key,IPC_CREAT|0600);
if(msgid == -1)
{
printf("creat que failed\n");
}
msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),888,0);
printf("read from que %s\n",readbuf.mtext);
msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。
#include
#include
shmget函数用来创建或获取一个共享内存。当用shmget函数创建一段共享内存是,必须指定其size,而如果引用一个已经存在的共享内存,则将其size指定为0。
当一段共享内存被创建之后,它并不能被任何进程访问,必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
shmdt函数是用来断开shmat建立的连接的。注意这并不是从系统中删除该内存,只是当前进程不能再访问该共享内存而已。
shmctl函数可以对共享内存执行多种操作,根据参数cmd执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。
int shmget(key_t key, size_t size, int shmflg); 成功返回共享内存ID,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg); 成功返回指向共享内存的指针,失败返回-1
int shmdt(const void *shmaddr); 成功返回0,失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf); 成功返回0,失败返回-1
写:
#include
#include
#include
#include
#include
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
int main()
{
int shmid;
key_t key;
char *shmaddr;
key=ftok(".",1);
shmid=shmget(key,1024*4,IPC_CREAT|0666);
if(shmid ==-1)
{
printf("shm create failed\n");
exit(-1);
}
shmaddr=shmat(shmid,0,0);
printf("shmat ok\n");
strcpy(shmaddr,"xxxxx");
sleep(5);
shmdt(shmaddr);
shmctl(shmid,IPC_RMID,0);
printf("quit shm\n");
return 0;
}
读:
#include
#include
#include
#include
#include
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
int main()
{
int shmid;
key_t key;
char *shmaddr;
key=ftok(".",1);
shmid=shmget(key,1024*4,0);
if(shmid ==-1)
{
printf("shm create failed\n");
exit(-1);
}
shmaddr=shmat(shmid,0,0);
printf("shmat ok\n");
printf("data: %s\n",shmaddr);
shmdt(shmaddr);
printf("quit shm\n");
return 0;
}
结果:
在内存共享中,两个进程都可以随意的对物理内存进行读写,但是,当一个进程正在写时,另一个进程又开始写,就会导致内存中的内容发生错乱,而信号就是为了防止这种情况,当某一个进程正在写时,其他进程只能读。
信号类似于软中断,许多重要的程序都需要处理信号。信号为Linux提供了一种处理异步事件的方法,比如终端用户输入ctrl+c来中断程序,会通过信号机制来停止一个程序。
更详细的信号内容见下面的文章:
Linux信号详解
signal.h
头文件中,信号名都定义为正整数。具体的信号名称可以使用kill -l
来查看信号的名字以及序号。序号 是从1开始的。SIGKILL
和SIGSTOP
)。#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
ctrl+c在Linux中属于SIGINT,只要捕捉这个信号,再写一个函数对它进行处理,不让程序退出就可以了
使用ps -aux | grep a.out查找进程ID,再使用kill -9 进程ID杀死该进程。
但是需要注意SIGKILL是不能用这种方式来不让进程中断的,也是不能被忽略的。
#include
#include
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
printf("get signalNum=%d\n",signum);
printf("nver quite\n");
}
int main()
{
signal(SIGINT,handler);//signal(SIGINT,SIG_IGN)可以忽略ctrl+c
while(1);
return 0;
}
#include
#include
#include
//int kill(pid_t pid, int sig);
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
int main(int argc ,char **argv)
{
int signum;
int pid;
signum=atoi(argv[1]); //atoi:ASCII码转换成int型
pid =atoi(argv[2]);
kill(pid, signum);
printf("send signal ok\n");
printf("num=%d,pid=%d\n",signum,pid);
return 0;
}
~
在前面信号章节中,只关注了收和发的两个动作,信号其实在发送、接收的时候还可以携带消息。
#include
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
signum:信号的编号
*act:类似signal的handler参数
*oldact:备份原有信号的操作
#include
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
pid:发给哪个进程
sig:发的信号
value:消息
以下是一些参数详解:
#include
#include
#include
//int kill(pid_t pid, int sig);
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
//int sigaction(int signum, const struct sigaction *act,
// struct sigaction *oldact);
void handler(int signum, siginfo_t *info, void *context)
{
printf("get signum %d\n",signum);
if(context != NULL)
{
printf("get data =%d \n",info->si_value.sival_int);
printf("get data =%d \n",info->si_int);
printf("senc pid is %d \n",info->si_pid);
}
}
int main(int argc ,char **argv)
{
int signum;
struct sigaction act;
printf("rev pid id %d \n",getpid());
act.sa_sigaction=handler;
act.sa_flags=SA_SIGINFO;
sigaction(SIGUSR1, &act ,NULL);
while(1);
return 0;
}
#include
#include
#include
//int kill(pid_t pid, int sig);
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
//int sigaction(int signum, const struct sigaction *act,
// struct sigaction *oldact);
int main(int argc ,char **argv)
{
int signum=atoi(argv[1]);
int pid=atoi(argv[2]);
union sigval value;
value.sival_int=100;
sigqueue(pid,signum,value);
printf("send done\n");
printf("send pid id %d \n",getpid());
while(1);
return 0;
}
信号量虽然和信号只差了一个字,但是它们是两种不同的概念。信号量(semaphore)与前面的IPC结构不同,它是一个计数器。信号量用于实现进程间的互斥同步,而不是用于存储进程间的通信数据。
各进程采取互斥的方式,实现共享的资源称为临界资源。多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次智能提供一个进程使用。一次仅允许一个 进程使用得资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。
最简单的信号量只能取0和1的变量,这也是信号量最常见的一种形式,叫做二值信号量。而可以取多个正整数的信号量被称为通用信号量。
Linux下的信号量函数都是再统一的信号量数组上进行操作的,而不是在一个单一的二值信号量上操作。
#include
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);
num_sems:信号量集中信号量的个数
sem_flags:获取信号量的时候一些权限,类似文件创建时的操作
控制子进程新运行,子进程运行完毕后,再运行父进程。
实现思路,先不放信号,等到子进程运行后放信号,在此期间父进程一直阻塞在取信号量的过程中,直到子进程放好信号量后,父进程才运行。
#include
#include
#include
#include
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, unsigned nsops);
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pGetKey(int id)
{
struct sembuf set;
set.sem_num=0;
set.sem_op=-1;
set.sem_flg=SEM_UNDO;
semop(id ,&set ,1);
printf("get the key\n");
}
void vPutBackKey(int id)
{
struct sembuf set;
set.sem_num=0;
set.sem_op=1;
set.sem_flg=SEM_UNDO;
semop(id ,&set ,1);
printf("put back the key\n");
}
int main(int argc,char **argv)
{
key_t key;
int semid;
key = ftok(".",2);
semid =semget(key,1,IPC_CREAT|0666);//creat and get semaphore
union semun initsem;
initsem.val=0;
semctl(semid,0,SETVAL,initsem);
//init sem,0 means operate the 0th semaphore
//SETVAL uses to set the value of the semaphore
int pid =fork();
if(pid>0)
{
pGetKey(semid);
printf("this is father\n");
vPutBackKey(semid);
}
if(pid ==0)
{
printf("this is child\n");
vPutBackKey(semid);
}
else
{
printf("fork faile\n");
}
return 0;
}