概述
进程控制原语,并且观察了如何调用多个进程。但是这些进程之间交换信息的唯一途径就是传送打开文件,经由fork或exec来传送,也可以通过文件系统来传送。本文将说明进程之间相互通信的其他技术——进程间通信(InterProcess Communication,IPC).IPC的方式通常有管道(无名管道和命名管道)、消息队列、共享内存、信号、信号量、Socket、stream(前五个为单机,后两个为多机).
1.管道
管道通常也称无名管道,是最古老的通信方式。
特点:1.它是半双工的,具有固定的读端和写段。2.它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程)。3.它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
fd[0]为读端,fd[1]为写端。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int pid;
int fd[2];
char readBuf[1024] = {0};
// int pipe(int pipefd[2]);
if(pipe(fd) == -1)
{
printf("create pipe failedn");
}
pid = fork();
if( pid < 0 )
{
printf("create fork failedn");
}
else if(pid > 0)
{
sleep(3);
printf("there is fathern");
close(fd[0]);//关闭读端,只能写
write(fd[1],"yi shu wei ban",strlen("yi shu wei ban"));
wait();
}
else if(pid == 0)
{
printf("there is childn");
close(fd[1]);//关闭写端,只能读
read(fd[0],readBuf,1024);
printf("read from father : %sn",readBuf);
exit(0);
}
return 0;
}
2.命名管道
管道具有两种局限性:1.它们是半双工的。2.管道只能在具有公共祖先的两个进程之间使用。通常,一个管道由一个进程创建,在进程调用fork之后,这个管道就只能在父子进程间使用。
命名管道FIFO没有第二种局限性。
创建管道并打开管道
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int main()
{
// int mkfifo(const char *pathname, mode_t mode);
if(mkfifo("./file1",0600) == -1 && errno != EEXIST)
{
printf("creat filedn");
perror("why");
}
open("./file1",O_RDONLY);
printf("open successn");
return 0;
}
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK
)的区别:
-
若没有指定
O_NONBLOCK
(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。 -
若指定了
O_NONBLOCK
,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int main()
{
// int mkfifo(const char *pathname, mode_t mode);
open("./file1",O_WRONLY);
printf("write open successn");
return 0;
}
通过管道传递信息:
read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int main()
{
int n_read = 0;
int fd;
// int mkfifo(const char *pathname, mode_t mode);
char readBuf[30] = {0};
fd = open("./file1",O_RDONLY);
while(1)
{
n_read = read(fd,readBuf,30);
printf("read %d byte from fifo,context : %sn",n_read,readBuf);
}
close(fd);
return 0;
}
write.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
int fd;
char *str = "meesage from fifo";
// int mkfifo(const char *pathname, mode_t mode);
fd = open("./file1",O_WRONLY);
printf("write open successn");
while(1)
{
sleep(1);
write(fd,str,strlen(str));
}
close(fd);
return 0;
}
3.消息队列
消息队列:是消息的链接表,存放在内核。一个消息队列由一个标识符(即队列ID)来标识。
特点:1.消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
2.消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
3.消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
long mtype;//类型
char mtext[128];//内容
};
int main()
{
struct msgbuf readbuf;
struct msgbuf writebuf = {988,"thank you"};
int msqid;
key_t key;
key = ftok(".",1);
printf("key = %xn",key);
// int msgget(key_t key, int msgflg);
if(msqid = msgget(key,IPC_CREAT|0777) == -1)
{
printf("failedn");
perror("why");
}
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgrcv(msqid,&readbuf,sizeof(readbuf.mtext),888,0);
printf("read from que:%sn",readbuf.mtext);
msgsnd(msqid,&writebuf,strlen(writebuf.mtext),0);
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl(msqid,IPC_RMID,NULL);
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf writebuf = {888,"this is message from que"};
struct msgbuf readbuf;
int msqid;
key_t key;
key = ftok(".",1);
printf("key = %xn",key);
// int msgget(key_t key, int msgflg);
if(msqid = msgget(key,IPC_CREAT|0777) == -1)
{
printf("failedn");
perror("why");
}
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgsnd(msqid,&writebuf,strlen(writebuf.mtext),0);
msgrcv(msqid,&readbuf,sizeof(readbuf.mtext),988,0);
printf("return form get :%sn",readbuf.mtext);
msgctl(msqid, IPC_RMID,NULL);//在内核中删除队列
return 0;
}
4.共享内存
指两个或多个进程共享一个给定的存储区。(ipcs -m查看)
特点:1.共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
2.因为多个进程可以同时操作,所以需要进行同步。
3.信号量(同步)+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
写
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *shmaddr;
int shmid;
key_t key;
key = ftok(".",1);
// int shmget(key_t key, size_t size, int shmflg);
shmid = shmget(key,1024*4,IPC_CREAT|0666);//钥匙介质,大小,方式
if(shmid == -1)
{
printf("failedn");
perror("why");
exit(-1);
}
exit(0);
// void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr = shmat(shmid,0,0);//id,自动配置内存,模式(默认读写)
printf("shmat okn");
strcpy(shmaddr,"yi shu wei ban");
sleep(5);
// int shmdt(const void *shmaddr);
shmdt(shmaddr);
// int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl(shmid,IPC_RMID,0);
printf("quitn");
return 0;
}
读
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *shmaddr;
int shmid;
key_t key;
key = ftok(".",1);
// int shmget(key_t key, size_t size, int shmflg);
shmid = shmget(key,1024*4,0);//获取不创建
if(shmid == -1)
{
printf("failedn");
perror("why");
exit(-1);
}
// void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr = shmat(shmid,0,0);
printf("shmat okn");
printf("data is : %sn",shmaddr);
sleep(5);
// int shmdt(const void *shmaddr);
shmdt(shmaddr);
// int shmctl(int shmid, int cmd, struct shmid_ds *buf);
printf("quitn");
return 0;
}
5.信号
对于Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为Linux提供了处理异步事件的方法。比如,终端用户输入了ctrl+c来中断程序,会通过信号机制停止一个程序。
每个信号都有一个名字和编号,可以使用(kill -l)查看信号的名字以及序号。信号的处理有三种方法,分别是:忽略、捕捉和默认动作。
- 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(
SIGKILL
和SIGSTOP
)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景 - 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
- 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
信号注册函数:
#include <stdio.h>
#include <signal.h>
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
printf("get signum = %dn",signum);
switch(signum)
{
case 2:
printf("SIGINTn");
break;
case 9:
printf("SIGKILLn");
break;
case 10:
printf("SIGUSR1n");
break;
}
}
int main()
{
signal(SIGINT,SIG_IGN);
signal(SIGKILL,SIG_IGN);
signal(SIGUSR1,handler);//信号捕捉
while(1);
return 0;
}
信号发送函数:
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc,char **argv)
{
int pid;
int signum;
char cmd[128] = {0};
signum = atoi(argv[1]);//转换
pid = atoi(argv[2]);//转换
printf("num = %d,pid = %dn",signum,pid);
// int kill(pid_t pid, int sig);
kill(pid,signum);//kill指令发送信号
// sprintf(cmd,"kill -%d %dn",signum,pid); //构造字符串
// system(cmd);
printf("kill okn");
return 0;
}
高级信号(在发送信号的同时还可以携带信息)
信号注册函数:
关于sigaction函数做下注释:1.信号号码或名字2.结构体3.备份
(1).结构体中(*sa_handler)(int)与简单信号的handler相似,不传递其他额外数据。
(2)信号处理函数,接受额外数据。括号里又包含3个类型:1.num2.结构体siginfo_t3.指针:有内容非空,无内容为空。
(3)阻塞信号集(默认阻塞)
(4)表示能够接受数据。
结构体siginfo_t:
处理
#include <stdio.h>
#include <signal.h>
// int sigaction(int signum, const struct sigaction *act,
// struct sigaction *oldact);
void handler(int signum,siginfo_t *info, void *context)
{
printf("get signum %dn",signum);
if(context != NULL)
{
printf("get data = %dn",info->si_int);
printf("get data = %dn",info->si_value.sival_int);
printf("get pid = %dn",info->si_pid);
}
}
int main()
{
struct sigaction act;
printf("pid is %dn",getpid());
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
发送
#include <stdio.h>
#include <signal.h>
int main(int argc,char **argv)
{
union sigval value;
int pid;
int signum;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
value.sival_int = 100;
// int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue(pid,signum,value);
printf("pid is %dn",getpid());
printf("donen");
return 0;
}
6.信号量
信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
// 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) */
};
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
void PGetKey(int id)//P操作
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
//个数
}
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
void VGet_back_Key(int id)//V操作
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
}
int main()
{
int pid;
int semid;
union semun initsem;
key_t key;
key = ftok(".",2);
// int semget(key_t key, int nsems, int semflg);//第二个参数:信号量集当中信号量的个数
semid = semget(key,1,IPC_CREAT|0666);
initsem.val = 0;
// int semctl(int semid, int semnum, int cmd, ...);
//操作第0个信号量
semctl(semid,0,SETVAL,initsem);//初始化信号量
//setval设置信号量的值
pid = fork();
if(pid >0)
{
PGetKey(semid);
printf("this is fathern");
VGet_back_Key(semid);
semctl(semid,0, IPC_RMID);
}
else if(pid == 0)
{
printf("this is childn");
VGet_back_Key(semid);
}
else
{
printf("errorn");
}
return 0;
}
最后
以上就是凶狠过客为你收集整理的Linux系统进程间通信编程的全部内容,希望文章能够帮你解决Linux系统进程间通信编程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复