Linux-进程间通信-有名管道-无名管道-信号量

一.有名管道

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命令可以进行打印消息队列,共享内存段,信号量数组信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值