关于进程间通信的学习总结

3 篇文章 0 订阅
linux的进程间通信:


一、半双工的管道:
1、半双工unix管道:
pipe(fd)
进程创建管道时,内核创建两个文件描述符。一个用于写,另一个用于读。创建管道的进程只能用该管道与自己通信。
而父进程创建子进程后,子进程会继承父进程的文件描述符,也就可以进行和父进程通信了。(限于父子进程的通信,而且管道通信直接与内核打交道)


举例:
#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>


using namespace std;


int main()
{
int fd[2];
pipe(fd); //必须在fork()之前,否则子进程没法继承父进程的文件描述符
pid_t pid;
pid = fork();
if(pid == -1)
cout<<"fork ocur error"<<endl;
if(0 == pid)
{
char str[] = "child connect parent process";


cout<<"This is child process"<<endl;
close(fd[0]);//关闭fd[0]
write(fd[1],str,strlen(str));
exit(0);
}
else
{
char rstr[1024];
memset(rstr,0,1024);
cout<<"This is parent process"<<endl;
close(fd[1]);
read(fd[0],rstr,strlen(rstr));
cout<<rstr<<endl;
}
return 0;
}


2、使用简便的高级管道:
FILE *fp = popen("str","r/w")

说明:使用popen直接使用管道打开另一个进程,可以直接使用shell命令。

参数:第一个是执行的操作命令字符创,第二个是操作的流类型(如果是r,返回的是stdin流类型,w返回的是stdout流类型)

举例:

#include<stdio.h>
#include<string>
int main()
{
string str = "ps -ef |grep -w `whoami`";


char outbuf[1024];
memset(outbuf,0,1024);


FILE* fstdin =NULL;


if(fstdin = popen(str.c_str,"r") == -1);
{
perror("popen ocurs error");
exit(1);
}
else
{
while(fgets(outbuf,1024,fstdin))
{
if(strncmp(outbuf,"oraname",sizeof("oraname"))==0)
{
printf("The value eque :%s",outbuf);
break;
}


}
pclose(fstdin);
exit(0);
}


return 0;
}

3、管道的原子操作
管道的原子操作的最大缓存是有限制的,linux的配置在 linux/limits.h中的
#define PIPE_BUF 4096


二、命名管道:


1、命名管道与半双工管道的区别:
在文件系统中命名管道时以设备特殊文件的形式存在的(半双工是内核直接调用的)
在不同家族的进程可以通过命名管道共享数据(也就是不仅仅父子进程)

mknod MYFIFO  p
或者
mkfifo a=rw MYFIFO

在c语言中使用:mknod("/tmp/MYFIFO",S_IFIFO|0666,0);

举例:
创建命名管道,然后阻塞读取管道文件的内容
服务端进程:
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<unistd.h>
#include<linux/stat.h>


#define FIFO_FILE "MYFIFO"
int main()
{
FILE* fp =NULL;
char readbuf[256];
memset(readbuf,0,256);


umask(0);
mknod(FIFO_FILE,S_IFIFO|0666,0);


while(1)
{
fp = fopen(FIFO_FILE,"r");
if(fp ==NULL)
{
perror("fopen ocurs error");
fclose(fp);
exit(1);
}
fgets(readbuf,256,fp);
printf("service readbuf is: %s",readbuf);
fclose(fp);
}
return 0;
}




客户端进程:
#include<stdio.h>
#include<stdlib.h>


#define FIFO_FILE "MYFIFO"


int main(int argc,char* argv[])
{
FILE* fd = NULL;
//char writebuf[256];
//memset(writebuf,0,256);


fd = fopen(FIFO_FILE,"w");
if(fd == NULL)
{
perror("open file ocurs error");
exit(1);
}
fputs(argv[1],fd);
fclose(fd);
return 0;
}




三、消息队列:内核地址空间中的内部链表

1、使用IPC标识符,此标识符在消息队列中是独一无二的,这样通过标识符就可以确定访问

2、使用关键字key_t,可以获取IPC标识符。

(ipcs命令可以显示当前所有的IPC信息,ipcrm删除一个IPC信息)

一般使用步骤:

a、获取可用的关键字

key_t key = ftok();
(也可以不使用这个函数,我们自己定义一个消息类型,然后由msgget检查并创建)
b、获取关键字对应的msgid(标识符)

int msgget(key_t key,int msgflag)
其中的msgflag主要参数:IPC_CREAT、IPC_EXECL
失败返回-1
如:
int msgid = msgget(key,IPC_CREAT|0660)

c、向消息队列中发送消息,这样内存中就有发送的数据了(一般是发送的进程)

int msgsnd(int msgid,struct msgbuf* msg,int msgsz,int msgflag)

其中struct msgbuf结构体是我们重载的消息结构体
msgflag表示是否需要阻塞(0表示忽略,IPC_NOWAIT表示立即中断,直到可以写消息)
如:
msgsnd(msgid,mymsgbuf,sizeof(mymsgbuf),0)

d、从消息队列中获取消息(一般是接收的进程)

int msgrcv(int msgid,struct msgbuf* msg,int msgsize,long mtype,int msgflag)



常见的几个重要的msg的struct:

a.消息的内存结构,用户自己可定义buf
struct msgbuf
{
long mtype;
char msg_text[1];
};
b.消息内核处理的基本结构(类似于一个节点):
struct msg
{
struct msg* msg_next;
long msg_type;
char* msg_spot; //消息体的指针头
short msg_ts; //消息体的大小
};

c.msgid_ds结构体,存放消息处理过程的数据(使用msgctl(int msgid,int cmd(IPC_STAT/IPC_SET/IPC_RMID),struct msgid_ds* buf)来获取,修改,删除等操作)
struct msgid_ds
{
struct ipc_perm msg_perm;
struct msg* msg_first;
struct msg* msg_last;
time_t msg_stime;
time_t msg_rtime;
time_t msg_ctime;
****
};
d.ipc_perm结构体,描述内核ipc对象的许可信息。
struct ipc_perm
{
key_t key;
ushort uid;
ushort gid;
ushort cuid;
ushort cgid;
ushort mode;
ushort seq;
};

举例:
发送端:
#include<stdio.h>
#include<stdlib.h>
#include<sys/msg.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/ipc.h>


#define 80
#define MSGKEY 1024


struct mymsgbuf
{
long mtype;
char mtext[MAX_SEND_SIZE];
};


int main()
{
key_t key;
int msgid;
long msgtype;
char msgtext[MAX_SEND_SIZE];



struct mymsgbuf msgbuf;
/*
msgbuf.mtype = 0;
strcpy(msgbuf.mtext,"simple txt");
*/
//key = ftok(".",'m');




msgid = msgget(MSGKEY,IPC_EXECL);
if(msgid < 0)
{
msgid = msgget(MSGKEY,IPC_CREAT|0660);
if(msgid <0)
{
perror("msgget ocurs error");
exit(1);
}
}


while(1)
{
printf("input message type:");
scanf("%d",&msgtype);
if(msgtype = 0)
break;
printf("input message text:");
scanf("%s",msgtext);
msgbuf.mtype = msgtype;
strcpy(msgbuf.mtext,msgtext);


msgsnd(msgid,&msgbuf,sizeof(msgbuf)-sizeof(long),IPC_NOWAIT);
}

msgctl(msgid,IPC_RMID,0);


return 0;
}


接收端:


#include<stdio.h>
#include<stdlib.h>
#include<sys/msg.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/ipc.h>


#define MAX_RECV_SIZE 80
#define MSGKEY 1024


struct mymsgbuf
{
long mtype;
char msgtext[MAX_RECV_SIZE];
};


int main()
{
int msgid;
struct mymsgbuf recvbuf;
int size = sizeof(recvbuf) - sizeof(long);
msgid  = msgget(MSGKEY,IPC_EXECL);
if(msgid <0)
{
msgid = msgget(MSGKEY,IPC_CREAT|0660);
if(msgid <0)
{
perror("client create msg occurs error");
exit(1);
}
}
msgrcv(msgid,&recvbuf,size,0);
printf("msg text is :%s",recvbuf.msgtext);


return 0;
}












四、信号量:它是一种计数器,用来控制对多个进程共享的资源所进行的访问


编程的步骤与消息队列几乎一致,需要和消息队列进程区别:消息队列是内核的一个链表序列结构,我们将需要发送和接受内核链表的消息,来进行IPC通信。
而信号量是在共享的资源进行控制。

1、步骤:

获取信号量的标识符
int semget(key_t key,int nsems,int semflag) ---很好理解:第一个key值,表示哪个信息量,第二个是信号量集合的大小(相当于几个打印机),第三个获取信号量的方式(IPC_CREAT/IPC_EXCL)
当我们使用IPC_EXCL判断某个信号量是否存在时,那么信号量的大小就没有意义了。

初始化信号量的buf内容
union semun sem_union;
sem_union.val = 0; //初始化信号量的值为0
int semctl(int sem_id,0,SETVAL,sem_union)

进行信号量处理:
struct sembuf sem_b;
sem_b.sem_num =0;
sem_b.sem_op = -1;
sem_b.sem_flg =SEM_UNDO;
int semop(int sem_id,&sem_b,1);

释放信号量:
semctl(sem_id,0,IPC_RMID,sem_union)



实例:


#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<sys/ipc.h>
#include<unistd.h>


#define DELAY_TIME 3


union semun
{
int val;
struct semid_ds* buf;
unsigned short* array;
};


int init_sem(int sem_id,int init_value);
int del_sem(int sem_id);
int sem_p(int sem_id);
int sem_v(int sem_id);


int main()
{
pid_t result;
int sem_id;
sem_id = semget(ftok(".",'a'),1,0666|IPC_CREAT);
init_sem(sem_id,0);


result = fork();
if(result == -1)
{
perror("fork occurs error\n");
}
else if(result == 0)
{
printf("child process will exec....\n");
sleep(DELAY_TIME);
sem_v(sem_id);
}
else
{
printf("parent process will exec...\n");
sem_p(sem_id);
printf("parent process exec");
sem_v(sem_id);
del_sem(sem_id);
}
exit(0);
}


int init_sem(int sem_id,int init_value)
{
union semun sem_union;
sem_union.val = init_value;
if(semctl(sem_id,0,SETVAL,sem_union))
{
perror("Initialize semaphore");
return -1;
}
return 0;
}
int del_sem(int sem_id)
{
union semun sem_union;
if(semctl(sem_id,0,IPC_RMID,sem_union) == -1)
{
perror("Delete Semaphore");
return -1;
}
return 0;
}


int sem_p(int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op  = -1;
sem_b.sem_flg = SEM_UNDO;


if(semop(sem_id,&sem_b,1) == -1)//对一个信号量进程-1操作
{
perror("p operation");
return -1;
}
return 0;
}


int sem_v(int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num =0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;


if(semop(sem_id,&sem_b,1) == -1)
{
perror("V operation");
return -1;
}
return 0;
}




五、共享内存
1、概念:内存映射的共享区域,是最快的IPC方式,映射的内存段可以被某个进程创建,其他进程可以随意读随意写,但是在写时最好是用信号量的方式进行锁住,防止交叉感染。


2、简单的几个内存结构:
struct shmid_ds
{
struct ipc_perm shm_perm;
int shm_segsz;
time_t shm_atime;
time_t shm_dtime;
time_t shm_ctime;
unsigned short shm_cpid;//create pid
unsigned short shm_lpid;//pid of last operator
****
}
这个结构体的重要性不用说了,如何获取或者设置对应的值呢?和前面一样也是通过shmctl()函数进行处理

这个结构体也就不说了,基本都是共享内存的许可信息
struct ipc_perm
{
}

int shmctl(int shmid,int cmd,struct shmid_ds* buf);

使用的方法:
IPC_STAT 获取buf的信息
IPC_SET 设置shmid_ds结构体的ipc_perm的成员值
IPC_RMID 标记shmid用于删除,此时不会删除,需要调用shmdt(char* shmaddr)来删除。


相对于前面的几种,共享内存其实是最方便的一个,仅仅在没有进程竞争读写修改时最好。

3、举例:

创建共享内存的进程:
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>


#define MAX_SHM_SZIE 1024
#define SHM_KEY 888
int main()
{
int shm_id;
char* shmadd = NULL;
char  shmstr[64] = "sbdcsb";
//key_t key;
//key = ftok(".",'m');
if(shm_id = shmget(SHM_KEY,MAX_SHM_SZIE,IPC_CREAT|0660|IPC_EXCL) < 0)
{
if(shm_id = shmget(SHM_KEY,MAX_SHM_SZIE,0))
{
perror("shmget occours error");
exit(1);
}
}
shmadd = shmat(shm_id,0,0);//挂接地址系统自己选取,方式默认
if(shmadd == NULL)
{
perror("shmat occours error");
exit(1);
}

//没有进行共享内存的删除,因为其他进程需要使用
//可以通过判断shm的内存数据来判断是否需要删除,
//比如内存的第一个字符表示是否删除标识

shmadd[0] = 'N';//初始化不删除,后面其他进程可以设置是否删除


strcpy(shmadd+1,shmstr);//进行内存赋值操作
while(1)
{
if(shmadd[0] == 'Y')
{
shmctl(shm_id,RMID,0)
shmdt(shmadd);
break;
}
}


return 0;
}

使用共享内存的进程:



#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>


#define MAX_SHM_SZIE 1024
#define SHM_KEY 888
int main()
{
int shm_id;
char* shmadd = NULL;
char  shmstr[64] = "sbdcsb";
//key_t key;
//key = ftok(".",'m');
if(shm_id = shmget(SHM_KEY,0,IPC_EXCL) < 0)
{
perror("shmget occurs error");
exit(1);
}
shmadd = shmat(shm_id,0,0);//挂接地址系统自己选取,方式默认
if(shmadd == NULL)
{
perror("shmat occours error");
exit(1);
}

//没有进行共享内存的删除,因为其他进程需要使用
//可以通过判断shm的内存数据来判断是否需要删除,
//比如内存的第一个字符表示是否删除标识

printf("shm content is :%s",shmadd+1);


//将内存头设置为Y

sleep(1);
shmadd[0] = 'Y';


return 0;
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值