一.有名管道
1.有名管道可以在任意两个进程间通信。(包括父子进程)
2.创建管道:mkfifo FIFO(FIFO为管道名称,可以换其他名称)
3.通信间进程(IPC机制):管道,信号量,共享内存,消息队列,套接字
4.正常创建的文件是在磁盘上存储,速度比较慢,在管道创建文件实际是在内核上,内存调用速度快
5.写端关闭,读端read()==0 ; open()必须两个进程同时进行,而且必须是一读一写 (管道 满 write阻塞 管道 空 read阻塞)
管道(pipe)是一种进程间通信(IPC)机制,通常由两个文件描述符组成。这两个文件描述符分别是管道的读端和写端。在创建管道时,这两个文件描述符分别存储在数组的第0和第1个位置。以下是对管道文件描述符数组索引的详细解释:
创建管道
创建管道时,通常使用 pipe 系统调用:
int pipefd[2];
int ret = pipe(pipefd);
if (ret == -1) {
perror("pipe");
return 1;
}
pipe 函数创建一个管道,并返回两个文件描述符:pipefd[0] 是管道的读端,pipefd[1] 是管道的写端。
管道的读写操作
在管道中,数据只能从写端写入,从读端读取。因此,pipefd[0] 用于读取数据,pipefd[1] 用于写入数据。
使用 send 或 write 向管道写入数据
要向管道写入数据,使用管道的写端文件描述符(pipefd[1])。例如:
int msg = 123;
write(pipefd[1], &msg, sizeof(msg));
使用 recv 或 read 从管道读取数据
要从管道读取数据,使用管道的读端文件描述符(pipefd[0])。例如:
int msg;
read(pipefd[0], &msg, sizeof(msg));
为什么使用 u_pipefd[1] 写入数据
u_pipefd[1] 是管道的写端文件描述符。
如果尝试向读端(u_pipefd[0])写入数据,会导致错误,因为管道只允许写入写端。
为什么不是 [0] 或 [2]
[0] 是管道的读端,只能用于读取数据,不能用于写入。
[2] 及其他索引通常无效,因为管道只创建两个文件描述符,存储在数组的第0和第1个位置。
写端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main()
{
int fd = open("fifo",O_WRONLY);//打开管道,得到的fd就是管道里面存放的文件描述符
if ( fd == -1 )//判断是否合法
{
exit(1);
}
printf("fd=%d\n",fd);//打印fd值
while( 1 )//循环输入
{
printf("input:\n");
char buff[128] = {0};//存在键盘上输入的值
fgets(buff,128,stdin);//在键盘上输入
if ( strncmp(buff,"end",3) == 0 )//若buff存end则直接退出循环,然后关闭fd
{
break;
}
write(fd,buff,strlen(buff));//写操作
}
close(fd);
exit(0);
}
读端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main()
{
int fd = open("fifo",O_RDONLY);//打开管道
if ( fd == -1 )//判断是否合法
{
exit(1);
}
printf("fd=%d\n",fd);
while( 1 )
{
char buff[128] = {0};//数据在buff中存的
int n = read(fd,buff,127);//读取buff中存放的值,并返回字符串长度大小(有'\0')
if ( n == 0 )//若读取的字符长度为0,则直接退出循环,关闭fd
{
break;
}
printf("buff=%s\n",buff);
}
close(fd);
exit(0);
}
二.无名管道
1.无名管道主要用于父子间进程的通信--->使用接口pipe(int fd[2]) //fd[0]存放读端 fd[1]中存放写端
2.有名和无名的区别:有名可以在任意两个进程间通信,无名只能在父子进程中
3.写入管道的数据在哪里:无论有名还是无名写入的数据都在内存中
4.管道的通信方式:半双工通信方式(通信方式一共有单工-->通信方式只能从一端到另一端,全双工-->双方可以同时通信而且一直是双向的,半双工-->通信方式可以是A到B也可以是B到A,但在某一时刻只能是一个方向)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main()
{
int fd[2];
if ( pipe(fd) == -1 )//返回值为-1,表示创建管道失败 pipe创建无名管道,返回读和写的描述符
{
exit(1);
}
pid_t pid = fork();
if(pid == -1 )
{
exit(1);
}
if ( pid == 0 )//因为是半双工父子进程均可以读和写,这里让子进程读,父进程写,也可以调换顺序,但不论父进程还是子进程都只能选择一个即读或写
{
close(fd[1]);//关闭写进程
char buff[128] = {0};//存放要读取的信息
read(fd[0],buff,127);//打开管道只读fd[0]将里面信息放在buff中
printf("buff=%s\n",buff);//打印buff
close(fd[0]);//关闭读操作
}
else
{
close(fd[0]);//关闭读操作
write(fd[1],"hello",5);//将5个字符的hello存放在只写管道fd[1]中
close(fd[1]);//关闭写操作
}
exit(0);
}
三.信号量(同步进程,控制进程执行)
1.信号量是一个特殊的变量 P操作-->代表获取资源 需要对信号量的值进行减一 信号量为0,表示没有资源可以用P操作会阻塞 V操作-->代表释放资源 需要对信号量的值进行加一 信号量的值如果只取0,1,称为2值信号量;信号量的值大于1,称为计数信号量。
2.临界资源:同一时刻只允许一个进程访问的资源
3.临界区:访问临界资源的代码段
4.①int semctl(int sem_id,int sem_num(对第几个信号量进行操作,一个信号量取0,下标为0),int command(命令可以选两个,见下面),...(信号量初始值)(参数可变是一个联合体可以有多个不同的数值));//初始化删除使用这个函数,这个联合体要自己定义。
② int semget(key_t key(A,B使用相同的整型值),int num_sems(创建几个信号量,基本都为1),int sem_flages(标志位));//创建一个信号量或者获取别人已经创建好的信号量
③ int semop(int sem_id,struct sembuf*sem_ops(结构体:用来描述对第几个信号量进行p,v操作,这个结构体不用自己定义),size_t num_sem_ops(第二个结构体的元素个数,只改变一个信号量的内容就为1)); //P,V操作使用这个
5.重点是如何同步,P,V操作该往哪里插(①要几个信号量②初始值是多少③p,v怎么加)
①信号量创建,初始化 sem_init()
②销毁信号量 sem_destroy()
③p操作 sem_p()
④v操作 sem_v()
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/sem.h>//semget函数
union semun//封装联合体
{
int val;//信号量的值
};
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
#include "sem.h"
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//全新创建一个信号量
if(semid == -1 )//失败,说明该信号量已经创建,
{
semid = semget((key_t)1234,1,0600);//获取别人创建的信号量
if( semid == -1 )
{
printf("semget err\n");
return;
}
}
else
{
union semun a;//这个联合体要自己定义
a.val = 1;//信号量初始化为1
if ( semctl(semid,0,SETVAL,a) == -1 )//初始化函数,a为联合体参数即初始化的值是可选的,也可以是其他参数
{
printf("semctl init err\n");
}
}
}
void sem_p()
{
struct sembuf buf;//这个结构体不用自己定义
buf.sem_num = 0;//代表对第几个信号量进行操作
buf.sem_op = -1;//p操作进行-1
buf.sem_flg = SEM_UNDO;//设置成这个可以让操作系统跟踪信号量
if ( semop(semid,&buf,1) == -1 )
{
printf("p err\n");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;//v操作进行+1
buf.sem_flg = SEM_UNDO;//设置成这个可以让操作系统跟踪信号量
if ( semop(semid,&buf,1) == -1 )
{
printf("v err\n");
}
}
void sem_destroy()
{
if ( semctl(semid,0,IPC_RMID) == -1 )
{
printf("semctl rm err\n");
}
}
测试上面信号量:
①加了信号量,打印结果AABBBBAA等等符合预期打印结果
#include "sem.h"
int main()
{
sem_init();
for(int i = 0; i < 5; i++ )
{
sem_p();
printf("A");
fflush(stdout);
int n = rand() % 3;
sleep(n);
printf("A");
fflush(stdout);
sem_v();
n = rand() % 3;
sleep(n);
}
}
#include "sem.h"
int main()
{
sem_init();
for(int i = 0; i < 5; i++ )
{
sem_p();
printf("B");
fflush(stdout);
int n = rand() % 3;
sleep(n);
printf("B");
fflush(stdout);
sem_v();
n = rand() % 3;
sleep(n);
}
sleep(10);//等10秒确保A已经运行完了在进行销毁
sem_destroy();//在a.c或b.c中销毁都可以
}
运行方法:
6.运用ipcs命令可以进行打印消息队列,共享内存段,信号量数组信息。