管道
管道通信的流程:在一个进程中创建管道,创建子进程,关闭多余端口,使父子进程形成单向通道,进行数据传输。
创建匿名管道函数:
#include <unistd.h>
int pipe(int pipefd[2]);
示例:使用pip实现父子进程间通信,子进程作为写端,父进程读出内容并打印到终端。
解释:用到的write(int fd,const void *buf,size_t count)函数:把参数buf所指的内存写入count个字节到所指的文件fd内。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int tempFd[2];//定义文件描述符数组
int tempRet=pipe(tempFd);//创建管道
if(tempRet == -1){
perror("pipe");
exit(1);
}
pid_t tempPid=fork();
if(tempPid > 0){//父进程—读
close(tempFd[1]);//关闭写端
char tempBuf[64]={0};
tempRet = read(tempFd[0], tempBuf, sizeof(tempBuf));//读数据
close(tempFd[0]);
write(STDOUT_FILENO, tempBuf, tempRet);//将读到的数据写到标准输出
wait(NULL);
} else if(tempPid == 0){//子进程—写
close(tempFd[0]);//关闭读端
char *tempStr="There is content:hello,pipe\n";
write(tempFd[1], tempStr, strlen(tempStr)+1);//写数据
close(tempFd[1]);
}//of if
return 0;
}//of main
输出结果:子进程将内容写入管道,父进程从管道读出内容。
重定向函数:将参数oldfd的文件描述符传递给newfd
#include <unistd.h>
int dup2(int oldfd, int newfd);
示例:使用管道完成兄弟间通信,实现命令 ls | wc -l
解释一下命令 ls|wc-l 实现的功能:ls列出当前文件夹下的文件,通过管道传递内容实现统计管道传来的文件数量,如下:
通过代码程序实现命令功能:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int tempFd[2];
int tempRet = pipe(tempFd);
if(tempRet == -1){
perror("pipe err");
exit(1);
}//of if
int i;
pid_t tempPid, tempWpid;
for(i=0; i<2; i++){//2个子
if((tempPid = fork()) == 0){
break;
}//of if
}//of if
if(2 == i){//父进程,回收子进程
close(tempFd[0]); //关闭读
close(tempFd[1]); //关闭写
tempWpid = wait(NULL);
printf("wait child 1 success,pid=%d\n", tempWpid );
tempPid = wait(NULL);
printf("wait child 2 success,pid=%d\n", tempPid);
} else if(0 == i){//子进程1—写
close(tempFd[0]);
dup2(tempFd[1], STDOUT_FILENO);//定向到标准输出
execlp("ls", "ls", NULL);
} else if(1 == i){//子进程2—读
close(tempFd[1]);
dup2(tempFd[0], STDIN_FILENO);
execlp("wc", "wc", "-l", NULL);
}//of if
return 0;
}//of main
运行结果:
linux标准I/O库中封装的两个函数完成管道通信:
#include <stdio.h>
//创建管道,创建子进程,调用shell命令
FILE *popen(const char *command, const char *type);
//关闭popen打开的I/O流,等待子进程执行结束,返回终止状态
int pclose(FILE *stream);
示例:通过调用函数实现管道通信,实现 ls | wc -l
解释用到的函数:
//从文件读取一行送到缓冲区的首地址s,size是缓冲区的长度
char *fgets(char *s,int size,FILE *stream)
//向指定文件写入一个字符串
int fputs(const char *s,FILE *stream)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
FILE *tempRfp,*tempWfp;
char temBuf[100];
tempRfp = popen("ls","r"); //读取命令执行结果
tempWfp = popen("wc -l","w"); //将管道中的数据传递给进程
while(fgets(temBuf, sizeof(temBuf), tempRfp)!=NULL){
fputs(temBuf, tempWfp);
}//of while
pclose(tempRfp);
pclose(tempWfp);
return 0;
}//of main
运行结果:
命名管道(FIFO):
匿名管道没有名字,只能用于有亲缘关系的进程间通信。
命名管道与系统中的路径名关联,以文件形式存在,系统中的不同进程可以通过路径名访问文件实现通信。
创建命名管道函数:
#include <sys/type.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
示例:创建命名管道实现没有亲缘关系的进程间通信。
解释:使用fork()函数创建进程有亲缘关系,所以这里不能使用fork()函数创建进程,通过两段程序实现。
第一段:w.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int paraArgc,char *paraArgv[]){
if(paraArgc < 2){ //判断是否传入文件名
printf("./a.out fifoname\n");
exit(1);
}//of if
int tempRet = access(paraArgv[1], F_OK); //判断fifo文件是否存在
if(tempRet == -1){ //若fifo不存在就创建fifo
int tempFIFO = mkfifo(paraArgv[1], 0664);
if(tempFIFO == -1){ //判断文件是否创建成功
perror("mkfifo");
exit(1);
} else{
printf("fifo creat success!\n");
}//of if
}//of if
int tempFd = open(paraArgv[1], O_WRONLY); //读写方式打开
while(1){ //循环写入数据
char *tempStrp="hello,world!";
write(tempFd, tempStrp, strlen(tempStrp)+1);
sleep(1);
}//of while
close(tempFd);
return 0;
}//of main
第二段:r.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int paraArgc,char *paraArgv[]){
if(paraArgc < 2){ //判断是否传入文件名
printf("./a.out fifoname\n");
exit(1);
}//of if
int tempRet = access(paraArgv[1], F_OK); //判断fifo文件是否存在
if(tempRet == -1){ //若fifo不存在就创建fifo
int tempFIFO = mkfifo(paraArgv[1], 0664);
if(tempFIFO == -1){ //判断文件是否创建成功
perror("mkfifo");
exit(1);
} else{
printf("fifo creat success!\n");
}//of if
}//of if
int tempFd = open(paraArgv[1], O_RDONLY); //只读方式打开
if(tempFd == -1){
perror("open");
exit(1);
}//of if
while(1){ //不断读取fifo中的数据并打印
char temBuf[1024]={0};
read(tempFd, temBuf, sizeof(temBuf));
printf("buf=%s\n", temBuf);
}//of while
close(tempFd); //关闭文件
return 0;
}//of main
运行结果:w.c程序中使进程每隔一秒向管道中写入一条数据,r.c进程在终端每隔一秒读出打印信息。
消息队列
消息队列实现进程间通信流程:创建消息队列,发送消息到消息队列,从消息队列中读取数据,删除消息队列。
可以通过系统调用实现上面的流程:
//创建消息队列,返回值为该队列号
int msgget(key_t key, int msgflg);
//向指定消息队列发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//接受信息,msgtyp需要指明接受的数据type
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg
//为队列随机附加key,pathename为路径,id号可随意(1-255)
key_t ftok(const char *pathname, int proj_id);
//对指定消息队列进行控制
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
示例:使用消息队列实现不同进程间通信。
两段代码依次为send.c和recive.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <string.h>
#define MAX_TEXT 512
//消息结构体
struct my_msg_st{
long int my_msg_type; //消息类型
char anytext[MAX_TEXT]; //消息数据
};
int main() {
int tempIdx = 1;
int tempMsgid;
struct my_msg_st tempData;
char tempBuf[BUFSIZ]; //设置缓存变量
tempMsgid = msgget((key_t)1000, 0664 | IPC_CREAT);//创建消息队列
if (tempMsgid == -1){
perror("msgget err");
exit(-1);
}//of if
while (tempIdx < 5){ //发送消息
printf("enter some text:");
fgets(tempBuf, BUFSIZ, stdin);
tempData.my_msg_type = rand() % 3 + 1; //随机获取消息类型
strcpy(tempData.anytext, tempBuf);
//发送消息
if (msgsnd(tempMsgid, (void*)&tempData, sizeof(tempData), 0) == -1){
perror("msgsnd err");
exit(-1);
}//of if
tempIdx ++;
}//of while
return 0;
}//of main
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#define MAX_TEXT 512
struct my_msg_st{
long int my_msg_type;
char anytext[MAX_TEXT];
};
int main(){
int tempIdx = 1;
int tempMsgid;
struct my_msg_st tempData;
long int tempMsgToRcv = 0;
//rcv msg
tempMsgid = msgget((key_t)1000, 0664 | IPC_CREAT);//获取消息队列
if (tempMsgid == -1){
perror("msgget err");
exit(-1);
}//of if
while (tempIdx < 5){
//接收消息
if (msgrcv(tempMsgid, (void*)&tempData, BUFSIZ, tempMsgToRcv, 0) == -1){
perror("msgrcv err");
exit(-1);
}//of if
//打印消息
printf("msg type:%ld\n", tempData.my_msg_type);
printf("msg content is:%s", tempData.anytext);
tempIdx ++;
}//of while
//删除消息队列
if (msgctl(tempMsgid, IPC_RMID, 0) == -1){
perror("msgctl err");
exit(-1);
}//of if
exit(0);
}//of main
运行结果:每当输入一条信息,接收进程会打印出其内容,在程序中使用了循环限制,因此四条后进程终止。
信号量
不同的进程通过获取同一个信号量键值进行通信,实现进程间对资源的互斥访问。
使用信号量进行通信流程:创建或者获取信号量/信号集,初始化信号量,执行信号量P、V操作根据进程请求修改信号量数量,从系统中删除不需要的信号量。
三个系统调用接口实现以上流程:
//创建或获取信号量/信号集
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
//控制信号量/信号集
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
//改变信号量的值
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
示例:使用信号量实现父子进程同步,防止父子进程抢夺CPU。
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/wait.h>
//自定义共用体,使用semctl()函数参数cmd需要自定义共同体
union semu{
int val;
struct semid_ds* buf;
unsigned short* array;
struct seminfo* _buf;
};
static int semId;
//设置信号量值
static int setSemValue() {
union semu tempSemUnion;
tempSemUnion.val = 1;
if (semctl(semId, 0, SETVAL, tempSemUnion) == -1){
return 0;
}//of if
return 1;
}//of setSemValue
//p操作,获取信号量
static int semaphoreP() {
struct sembuf tempSemBuf;
tempSemBuf.sem_num = 0;
tempSemBuf.sem_op = -1;
tempSemBuf.sem_flg = SEM_UNDO;
if (semop(semId, &tempSemBuf, 1) == -1){
perror("sem_p err");
return 0;
}//of if
return 1;
}//of semaphoreP
//V操作,释放信号量
static int semaphoreV() {
struct sembuf tempSemBuf;
tempSemBuf.sem_num = 0;
tempSemBuf.sem_op = 1;
tempSemBuf.sem_flg = SEM_UNDO;
if (semop(semId, &tempSemBuf, 1) == -1){
perror("sem_v err");
return 0;
}//of if
return 1;
}//of semaphoreV
//删除信号量
static void delSemValue() {
union semu tempSemUnion;
if (semctl(semId, 0, IPC_RMID, tempSemUnion) == -1){
perror("del err");
}//of if
}//of delSemValue
int main() {
int i;
pid_t temPid;
char tempChar = 'C';
semId= semget((key_t)1000, 1, 0664 | IPC_CREAT);//创建信号量
if (semId== -1){
perror("sem_c err");
exit(-1);
}//of if
if (!setSemValue()){ //设置信号量值
perror("init err");
exit(-1);
}//of if
temPid = fork(); //创建子进程
if (temPid== -1){ //若创建失败
delSemValue(); //删除信号量
exit(-1);
} else if (temPid == 0){ //设置子进程打印的字符
tempChar = 'Z';
} else { //设置父进程打印的字符
tempChar = 'C';
}//of if
srand((unsigned int)getpid()); //设置随机数种子
for (i = 0; i < 8; i++) { //循环打印字符
semaphoreP(); //获取信号量
printf("%c", tempChar);
fflush(stdout); //将字符打印到屏幕
sleep(rand() % 4); //沉睡
printf("%c", tempChar);
fflush(stdout); //再次打印到屏幕
sleep(1);
semaphoreV(); //释放信号量
}//of for i
if (temPid > 0){
wait(NULL); //回收子进程
delSemValue(); //删除信号量
}else{
printf("\nI'm child,my ppid is: %d\n",getppid());
}//of if
printf("\nprocess %d finished.\n", getpid());
return 0;
}//of main
运行结果:
解释:使用semaphoreP()获取信号量,若获取信号量的是父进程,那么子进程无法获取CPU,除非父进程调用semaphoreV()释放信号量,子进程也是一样。因此循环内的两次打印操作为同一个结果,即打印结果的成对出现。打印z结束即子进程此时占有CPU,子进程执行完代码先结束,CPU分配给父进程父进程继续执行代码至结束。
共享内存
linux内核提供了相关系统调用函数:
//创建或打开一块共享内存
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
//将共享内存映射到进程虚拟地址空间中
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
//解除物理内存与进程虚拟地址的映射关系
#include <sys/shm.h>
int shmdt(const void *shmaddr);
//对已经存在的共享内存进行操作
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
示例:创建两个进程,使用共享内存机制实现进程间通信。
两段代码程序shw.c和shr.c
第一段shw.c:
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#define SEGSIZE 4096 //定义共享内存容量
typedef struct{ //读写数据结构体
char name[8];
int age;
} Stu;
int main() {
int tempShmId, i;
key_t tempKey;
char tempName[8];
Stu *tempSmap;
/*ftok函数的作用:系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。*/
tempKey = ftok("/", 0); //获取关键字
if (tempKey == -1) {
perror("ftok error");
return -1;
}//of if
printf("key=%d\n", tempKey);
//创建共享内存
tempShmId= shmget(tempKey, SEGSIZE, IPC_CREAT | IPC_EXCL | 0664);
if (tempShmId == -1) {
perror("create shared memory error\n");
return -1;
}//of if
printf("shm_id=%d\n", tempShmId);
tempSmap = (Stu*)shmat(tempShmId, NULL, 0); //将进程与共享内存绑定
memset(tempName, 0x00, sizeof(tempName));
strcpy(tempName, "Jhon");
tempName[4] = '0';
for (i = 0; i < 3; i++){ //写数据
tempName[4] += 1;
strncpy((tempSmap+ i)->name, tempName, 5);
(tempSmap + i)->age = 20 + i;
}//of for i
if (shmdt(tempSmap) == -1){ //解除绑定
perror("detach error");
return -1;
}//of if
return 0;
}//of main
第二段shr.c:
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{
char name[8];
int age;
} Stu;
int main() {
int tempShmId, i;
key_t tempKey;
Stu *tempSmap;
struct shmid_ds tempBuf;
tempKey = ftok("/", 0); //获取关键字
if (tempKey == -1) {
perror("ftok error");
return -1;
}//of if
printf("key=%d\n", tempKey);
tempShmId = shmget(tempKey, 0, 0); //创建共享内存
if (tempShmId == -1) {
perror("shmget error");
return -1;
}//of if
printf("shm_id=%d\n", tempShmId);
tempSmap = (Stu*)shmat(tempShmId, NULL, 0); //将进程与共享内存绑定
for (i = 0; i < 3; i++){ //读数据
printf("name:%s\n", (*(tempSmap + i)).name);
printf("age :%d\n", (*(tempSmap + i)).age);
}//of for i
if (shmdt(tempSmap) == -1){ //解除绑定
perror("detach error");
return -1;
}//of if
shmctl(tempShmId, IPC_RMID, &tempBuf); //删除共享内存
return 0;
}//of main
运行结果:执行第一段程序创建共享内存并写入数据,执行第二段程序读取数据且读取后删除共享内存,因此再次执行读取数据程序看到无法找到共享内存。