Linux讲解 进程间通信 信号量的互斥

41 篇文章 1 订阅
35 篇文章 5 订阅

  我们在介绍进程间间通信的时候说到了共享内存,共享内存有一个特点就是共享内存并没有提供同步和互斥机制的,是需要我们自己来实现共享内存访问的同步和互斥。

   首先要明白什么是同步与互斥。互斥就是:进程对临界资源的同一时间的唯一访问性。同步就是:进程对临界资源的顺序访问关系。通俗来说互斥就是一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。而同步就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。

  这里之前看到一个例子:我们都使用过打印机,但是我们有很多进程都可能会去调用我们的打印机,wps可以,word可以等等很多进程都可以去调用,但是打印机只有一个,所以操作系统就想到了一个办法,在操作系统中专门开辟了一块空间,谁想要打印东西按照先后顺序排队放在这里,但是这样之后就出现了一种冲突。Wps就来找打印机,为什么你打印了我前两个文件第三个xxx没有打印出来呢?打印机说我没有看到你这个文件啊,wps很纳闷我明明把我的文件放到了3号队列里怎么会没有呢?之后在操作系统的帮助下才发现了根本原因,是word在这个过程中查了一脚,当时word和wps同时进来打印,word读取到这块空间里边该往队列3里边放东西了,他就记下了,但是这时候word进程里边突然引起了一次时钟中断,导致操作系统感觉word这个进程只是想打印个东西,已经执行这么久了不该它运行了,就切换到了wps,wps这时候也看到了3位置是空的,就直接把文件放到这个3位置就走了,这时候再回来到word的时候,就也把文件放到了3号槽这个位置,这时候wps自身的文件就被覆盖掉了,打印机是什么都察觉不出来的。所以就出现了问题。

   很明显这两个进程在访问我们临界区的时候出现了问题,这次是wps下次可能就是word,这种对共享变量,共享内存,共享资源进行访问的程序片段就是我们平时说的临界区。所以代码在进入临界区的时候一定要做好同步和互斥操作。

  信号量就是用来实现我们上边说到的同步和互斥操作的,信号量说白了就是认为定义了一个变量,它只能被两个标准的原语wait(S) 和 signal(S) 来访问,也可以记为“P操作”和“V操作”。

信号量可以理解成一个计数器,用它来标志,当这个数值大于0的时候表示现在可以用的资源数,当数值等于0的时候代表这时候没有可用资源也没有在等待的进程,当数值小于0的时候,这时候数值的绝对值代表着等待的进程数,这是很容易理解的。

p操作进来就让我们能访问的资源值减去1,如果资源值小于0了那说明这时候没有资源可以访问,那你就去排队等着。

v操作就是去释放我们的资源,让我们可以访问的资源加1,如果这时候数值小于等于0那说明,有进程在等着资源,那你就要去唤醒等待的进程来访问刚刚释放掉的资源。

我们通过程序来实现信号量的互斥操作

//这是实现信号量互斥操作的程序



#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<errno.h>

#include<unistd.h>

#include<sys/ipc.h>

#include<sys/sem.h>



#define IPC_KEY 0X12345678

union sem {

    int val;//信号量数值

    struct semid_ds *buf;//内核所提供的结构体

    unsigned short *array;//

    struct seminfo *_buf;

};



void sem_P(int id)

{



    struct sembuf buf;

    //sembuf结构体包含三个变量

    //num代表信号量的编号

    //op代表一次pv操作改变的值一般是-1或者1,这里是p操作所以是-1也就是让资源量变少的

    //flg有两个选项SEM_UNDO是让操作系统跟踪信号,并在进程没有释放改信号而终止的时候,操作系统将其释放

    buf.sem_num=0;

    buf.sem_op=-1;

    buf.sem_flg=SEM_UNDO;



    semop(id,&buf,1);

    //semop是用来创建和访问一个信号量集

    //第一个是semop的操作信号量表示码也就是semget的返回值

    //第二个是指向一个结构数值的指针

    //第三个是信号量的个数

}

void sem_V(int id)

{

    struct sembuf buf;

    buf.sem_num=0;

    buf.sem_op=1;//这里是V操作是让信号量加1的所以这里是1

    buf.sem_flg=SEM_UNDO;

    semop(id,&buf,1);

}

int main()

{

    int semid=semget(IPC_KEY,1,IPC_CREAT|0664);

    //semget创建一个信号量

    //第一个参数是信号量的标识

    //第二个参数是要创建的信号量个数

    //第三个参数和之前创建文件管道等等的作用是一样的

    if(semid<0)

    {

        perror("semget error\n");

        return -1;

    }

    union sem semval;

    semval.val=1;

    semctl(semid,0,SETVAL,semval);

    //semctl函数给信号量设置初值并且只能设置一次

    //第一个参数是信号量的标识

    //第二个参数是操作第几个信号量

    //之后的参数是不固定的,是用来表示对信号量要做些什么的操作

    //SETVAL代表设置信号量的数值,STEALL代表设置所有信号量的数值这时候第二个参数将被忽略

    //如果要获取信号量的数值第四个参数就是结构体,如果要给信号量设置初值那么放得就是值的联合体

    int pid=-1;

    pid=fork();

    if(pid<0)

    {

        perror("fork error\n");

        return -1;

    }

    else

    {

        if(pid==0)

        {

            sleep(1);

            while(1)

            {

                sem_P(semid);

                //因为我们设置的是一元的信号量,所以在这里进行p操作的时候信号量的数值就会变成0

                //这时候其他进程就不能再去访问

                printf("A");

                fflush(stdout);

                sleep(1);

                printf("A");

                fflush(stdout);

                sem_V(semid);

                //我们操作完成后释放掉我们的信号量这时候其他进程就可以操作了

            }

        }

        else

        {

            while(1)

            {

                sem_P(semid);

                printf("B");

                fflush(stdout);

                sleep(1);

                printf("B");

                fflush(stdout);

                sem_V(semid);

            }

        }

    }



   return 0;

}

可以看出来在最初的时候我们让打印A的进程暂停了1s这段时间都是在打印的B等这段时间结束之后,就变成了我们的AABB有规律的在进行打印。这么看是感觉不出来的,我们现在通过一个没有互斥操作的程序来打印一下。

这就能看出来,我们两个B还没有打印完就去打印A了,两个A也没打印完因为进行了中断就去打印B了。这就出现了我们最初的时候提到的问题。

用完之后我们还是把它删除

这个是必须要删除的,无论进程怎么样他是不会自动清除的,除非重启系统,所以我们还是要养成良好的习惯所有的ipc都是要进行清除的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是课程列表: ├day01-01 系统介绍之缓冲区刷新.mp4 ├day01-02 系统介绍之man手册的使用.mp4 ├day02_文件查找规则.mp4 ├day03-01 调试输出点.mp4 ├day03-02 改变结构体对齐规则.mp4 ├day03-03 零长数组.mp4 ├day04-01 错误输出.mp4 ├day04-02 文件操作.mp4 ├day05-01 系统调用之文件操作.mp4 ├day05-02 C库函数对文件操作.mp4 ├day06-01 获取文件访问标识、文件加锁.mp4 ├day06-02 文件的访问权限、文件权限操作.mp4 ├day07-01链接文件操作.mp4 ├day07-02 目录操作.mp4 ├day08-01获取文件目录属性.mp4 ├day08-02 获取当前工作目录.mp4 ├day09-01 临时文件.mp4 ├day09-02 获取系统环境变.mp4 ├day10_环境变的增、删、改、查.mp4 ├day11-01 创建屏蔽字.mp4 ├day11-02 知识小结.mp4 ├day12-01 dup文件描述符复制.mp4 ├day12-02 dup2 文件描述符复制.mp4 ├day13_Mmap与文件关联映射.mp4 ├day14-01 匿名模式.mp4 ├day14-02 缓冲区(行缓冲 全缓冲 无缓冲).mp4 ├day14-03 获取进程id.mp4 ├day15-01 获取fork子父进程id.mp4 ├day15-02 子父进程 执行顺序 资源共享 资源回收.mp4 ├day16-01 ufork之子父进程 执行顺序 资源共享 资源回收.mp4 ├day16-02 孤儿进程.mp4 ├day16-04 守护进程讲解.mp4 ├day17-01 守护进程实现.mp4 ├day18-01 fork与sighal的组合(避免僵尸进程).mp4 ├day18-02 匿名管道之创建、缓冲区大小、阻塞模式.mp4 ├day18-03 知识点总结.mp4 ├day19_匿名管道子父进程通信、有名管道创建、删除.mp4 ├day20_有名管道的特点、在子父进程及非子父进程操作.mp4 ├day21-01 有名管道的创建、缓冲区大小、阻塞模式、信号.mp4 ├day21-02 有名管道进程间通信.mp4 ├day22_消息队列默认属性及改变方法.mp4 ├day23-01 消息队列、读写操作.mp4 ├day23-02 消息队列之mp-notify读操作.mp4 ├day23-03 消息队列之mp-tined-recv、mp-tined-sewd.mp4 ├day24-01 共享内存的读写操作.mp4 ├day24-02 共享内存与map的公用.mp4 ├day25-01 共享内存综合案例操作.mp4 ├day25-02 匿名信号量讲解使用.mp4 ├day25-03 匿名信号量同步.mp4 ├day26-01巩固知识点回顾与总结.mp4 ├day26-02 匿名信号量互斥.mp4 ├day26-03 线程id 线程比较.mp4 ├day27-01 线程的执行顺序 资源共享.mp4 ├day27-02 线程资源回收 线程变创建 线程属性.mp4 ├day27-03 线程栈空获取 线程中断.mp4 ├day28-01 线程及信号量组合同步、组合互斥.mp4
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户 /服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户 /服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变 条件变 条件变函数 条件变使用规范 使用条件变解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现 网络编程, Linux

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值