进程间通信

进程间通信

进程都是独立的个体,每个进程之间的数据是不共享的。

文件操作缺陷

  • 没办法保证写入数据在读取数据之前
  • 文件的存储空间是在磁盘上,写入数据时,要执行一次I/O操作,读取数据时执行一次I/O操作。
  • 只能做到有关系的进程之间数据的传递,而且没办法实现任意两个进程之间。

进程间通信的机制:管道(有名管道,无名管道) 信号量 共享内存 消息队列。

有名管道:在磁盘有一个管道文件标识,但是这个管道文件只会占据一个inode结点,但是这个管道文件任何时候都不会占据block块,数据在传递过程中会缓存在内存上。(管道文件仅仅是为了使不同的进程(有权限操作的))能够共享。

无名管道:没有管道文件,借助父子进程共享fork之前打开的文件描述符。来实现进程间通信。

管道

创建管道文件:

命令 mkfifo filename

库函数 int mkfifo(); open read write close

有名管道文件通信测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>

int main()
{
int fd=open("./FIFO",O_WRONLY);
assert(fd!=-1);

printf("write open fifo success\n");

while(1)
{
char buff[128]={0};
printf("please input: ");
fgets(buff,127,stdin);

if(strncmp(buff,"end",3)==0)
{
break;
}
write(fd,buff,strlen(buff)-1);
}
close(fd);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>

int main()
{
int fd=open("./FIFO",O_RDONLY);
assert(fd!=-1);

printf("read open fifo success\n");

while(1)
{
char buff[128]={0};
int n=read(fd,buff,127);
if(n<=0)
{
break;
}
printf("read: %s\n",buff);
}
close(fd);
}

  • open以一种方式打开管道文件会阻塞,直到有进程以另一种方式打开此管道文件。
  • 如果管道对应的内存空间中没有数据,则read会阻塞,直到(1)内存中有数据(2)所有写端关闭
  • 如果管道对应的内存空间已满,则write就会阻塞,直到(1)内存有空间(2)所有读端关闭

无名管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>

int main()
{
int fds[2];
int res=pipe(fds); //fds[0]读端,fds[1]写端
assert(res!=-1);

pid_t pid=fork();
assert(pid!=-1);

if(pid==0)
{
close(fds[1]);//子进程直接关闭管道的写端
while(1)
{
char buff[128]={0};
int n=read(fds[0],buff,127);
if(n<=0)
{
break;
}
printf("child: %s\n",buff);
}
close(fds[0]);
}
else
{
close(fds[0]);//父进程直接关闭管道的读端
while(1)
{
char buff[128]={0};
printf("input: ");
fgets(buff,127,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
write(fds[1],buff,strlen(buff)-1);
}
close(fds[1]);
}
}

消息队列

消息:类型+数据

队列:先进先出(优先级队列)

信号量 消息队列 共享内存—>内核对象(操作系统内核中)。

1
2
3
4
5
6
7
8
9
10
11
int msgget((key_t)key,int flag)
返回值:成功返回内核对象的ID值,失败返回-1
key:用户标识,如果多个进程想通过同一个消息队列完成数据通信,则每个进程使用相同的key值创建或者获取同一个消息队列的内核对象ID值。
flag:IPC_CREAT
int msgct(int msgid,int cmd,struct msqid_ds *buf);
cmd:IPC_RMID 删除消息队列
struct msgbuf
{
long mtype; //消息类型,必须大于0
char mtext[1];//消息数据
}

消息队列中的数据是有间隔的

1
2
3
4
5
6
7
8
9
10
11
12
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
typedef struct msgdata
{
long mtype;
char mdata[128];
}MsgData;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include"./msg.h"

int main()
{
int msgid=msgget((key_t)1234,IPC_CREAT|0664);
assert(msgid!=-1);

MsgData data;
memset(&data,0,sizeof(data));

msgrcv(msgid,&data,127,100,0);
printf("%d:%s\n",data.mtype,data.mdata);
exit(0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "./msg.h"

int main()
{
MsgData data;
memset(&data,0,sizeof(data));
data.mtype=100;
strcpy(data.mdata,"hello");

int msgid=msgget((key_t)1234,IPC_CREAT|0664);
assert(msgid!=-1);

msgsnd(msgid,&data,strlen(data.mdata),0);
exit(0);
}

信号量

对于进程执行的同步控制

关系:竞争关系 协作关系

特殊的一个变量(计数器),一般取正数值。他的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子-1,该操作被称为P操作。当信号量值为0时,代表没有资源可用,P操作会阻塞。释放资源时,需要对信号量的值进行原子+1,该操作被称为V操作。信号量主要用来同步进程。信号量的值如果只取0,1,将其称为二值信号量。如果信号量的值大于1,则称为计数信号量。

临界资源:同一时刻,只允许被一个进程或者线程访问的资源。

临界区:访问临界资源的代码段。

Linux系统内核维护是一个信号量集

1
2
int semget((key_t)key,int nsems,int flag)
flag:IPC_CREAT IPC_EXCL//指定一个操作权限==文件权限

如果是第一次执行semget,内核中没有信号量集,则需要创建信号量集,并且完成初始化。如果内核中已经有了此信号量集,则直接获取返回就可以。

1
2
3
4
5
6
7
8
9
10
int semctl(int semid,int semnum,int cmd,...)
semnum:信号量下标
cmd:TPC_RMID删除 SETVAL初始化
int semop(int semid,struct sembuf *buf,int len)
struct sembuf
{
int sem_num;//信号量下标
int sem_op;//-1 P 1 P
int sem_flg;//SEM_UNDO
};

信号量的操作

sem.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma once
#include<sys/sem.h>
union semval
{
int val;
};

int create_sem(int key,int init_val[],int len);
//一次只操作一个信号量
void sem_p(int semid,int index);
void sem_v(int semid,int index);

void delete_sem(int semid);

sem.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "./sem.h"
#include<stdio.h>

//1、如果内核有了此key值对应的信号量,则直接返回
//2、没有,创建此信号量集 所有的信号量根据init_val的值进行初始化
int create_sem(int key,int init_val[],int len)
{
//获取
int semid=semget((key_t)key,0,0664);
if(semid!=-1)
{
return semid;
}
//创建
semid=semget((key_t)key,len,IPC_CREAT|0664);
if(semid==-1)
{
perror("create sem error: ");
return -1;
}
int i=0;
for(;i<len;i++)
{
union semval data;
data.val=init_val[i];
if(-1 == semctl(semid,i,SETVAL,data))
{
perror("Init Sem Value Fail: ");
return -1;
}
}
return semid;
}
void sem_p(int semid,int index)
{
struct sembuf buf;
buf.sem_num=index;
buf.sem_op=-1;
buf.sem_flg=SEM_UNDO;

if(-1==semop(semid,&buf,1))
{
perror("sem p operation fail: ");
}
}
void sem_v(int semid,int index)
{
struct sembuf buf;
buf.sem_num=index;
buf.sem_op=1;
buf.sem_flg=SEM_UNDO;

if(-1==semop(semid,&buf,1))
{
perror("sem p operation fail: ");
}
}
void delete_sem(int semid)
{
if(-1==semctl(semid,0,IPC_RMID))
{
perror("delete sem fail: ");
}
}

maina.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<time.h>
#include"sem.h"

int main(int argc,char *argv[])
{
srand((unsigned int)(time(NULL)*time(NULL)));

int val=1;
int semid=create_sem(1234,&val,1);
assert(semid!=-1);
int count=0;
while(1)
{
sem_p(semid,0);
printf("%s\n",argv[1]);
int n=rand()%4;
sleep(n);
printf("%s\n",argv[1]);
sem_v(semid,0);

n=rand()%4;
sleep(n);
count++;
if(count==5)
{
break;
}
}
}

共享内存(最快的IPC)

1
2
3
4
5
6
7
1、获取
int shmget((key_t)key,int size,int flag);
void *shmat(int shmid,void *addr,int shmflg);
//返回一个虚拟地址,此虚拟地址就是映射到共享内存的首地址
int shmdt(void *addr);//断开连接
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
cmd:IPC_RMID//并不会立即删除共享空间,不能使用shmat方法与共享存储段建立映射关系
1
2
3
A进程获取用户数据,B进程打印用户数据
B进程必须在A进程获取数据之后才能打印
A进程必须在B进程将上一次数据处理之后才能再次释放

shma.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/shm.h>
#include"sem.h"

int main()
{
int shmid=shmget((key_t)1234,128,IPC_CREAT|0664);
assert(shmid!=-1);
char *ptr=(char *)shmat(shmid,NULL,0);
assert(ptr!=(char *)-1);
int init_val[2]={0,1};
int semid=create_sem(1234,init_val,2);
assert(semid!=-1);

while(1)
{
sem_p(semid,1);//B-->A
printf("input: ");
fgets(ptr,127,stdin);
sem_v(semid,0);//A-->B
if(strncmp(ptr,"end",3)==0)
{
break;
}

}
shmdt(ptr);
shmctl(shmid,IPC_RMID,NULL);
}

shmb.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/shm.h>
#include<time.h>
#include"sem.h"

int main()
{
srand((unsigned int)(time(NULL)*time(NULL)));
int shmid=shmget((key_t)1234,128,IPC_CREAT|0664);
assert(shmid!=-1);
char *ptr=(char *)shmat(shmid,NULL,0);
assert(ptr!=(char *)-1);

int init_val[2]={0,1};
int semid=create_sem(1234,init_val,2);
assert(semid!=-1);

while(1)
{
sem_p(semid,0);//A-->B
if(strncmp(ptr,"end",3)==0)
{
break;
}
printf("B process: %s",ptr);
int n=rand()%3+1;
sleep(n);
printf("B deal over\n");
memset(ptr,0,128);
sem_v(semid,1);//B-->A
if(strncmp(ptr,"end",3)==0)
{
break;
}

}
shmdt(ptr);
shmctl(shmid,IPC_RMID,NULL);
delete_sem(semid);
}