进程间通信

前言

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括匿名管道和命名管道)、消息队列、信号量、共享内存等。

在学习之前我们需要了解以下内容:
1.因为进程具有独立性,如果两个或者多个进程需要相互通信,就必须要看到同一份资源:就是一段内存!这个内存可能以文件的方式提供,也可能以队列的方式,也可能提供的就是原始的内存块!
2.这个公共资源应该属于谁?这个公共资源肯定不属于任何进程,如果这个资源属于进程,这个资源就不应该再让其它进程看到,要不然进程的独立性怎么保证呢?所以这个资源只能属于操作系统!
3.进程间通信的前提:是有OS参与,提供一份所有通信进程能看到的公共资源!


1、进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常

2、管道

管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个"管道"。
管道是一个只能单向通信的通信管道,如果想要双向通信,那么就建立两个管道。
管道的本质是内核中的一块缓冲区
在这里插入图片描述

1.1 匿名管道

在这里插入图片描述
参数:是一个输出型参数,通过这个参数读取到两个打开的fd
返回值:若创建成功则返回0,否则返回-1

pipedf[0] 和 pipedf[1] 哪一个是读,哪一个是写呢?
0:读取端 1:写入端。 我们可以把0想象成嘴巴, 把1想象成笔,这样就很容易记住了

#include<stdio.h>
#include<unistd.h>
int main()
{
    //pipefd[2]:是一个输出型参数!
    int pipefd[2] = {0};
    if(pipe(pipefd) != 0)
    {
        perror("pipe error!\n");
        return -1;
    }
    //pipedf[0], pipedf[1] 哪一个是读,哪一个是写呢?
    //0(嘴):读取端    1(笔):写入端
    printf("pipefd[0]: %d\n", pipefd[0]);//打印3,因为0,1,2已经被占用
    printf("pipefd[1]: %d\n", pipefd[1]);//打印4,因为0,1,2,3已经被占用
    return 0;
}

运行代码:
在这里插入图片描述
结果跟预想的一样是 3 和 4

看着样一段代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    //pipefd[2]:是一个输出型参数!
    int pipefd[2] = {0};
    if(pipe(pipefd) != 0)
    {
        perror("pipe error!\n");
        return -1;
    }
    //pipedf[0], pipedf[1] 哪一个是读,哪一个是写呢?
    //0(嘴):读取端    1(笔):写入端
    printf("pipefd[0]: %d\n", pipefd[0]);//打印3,因为0,1,2已经被占用
    printf("pipefd[1]: %d\n", pipefd[1]);//打印4,因为0,1,2,4已经被占用
    
    //我们让父进程读取,子进程写入
    if(fork() == 0)
    {
        //子进程
        close(pipefd[0]);//关闭读端,保留写端

        const char* msg = "hello fl";
        while(1)
        { 
            write(pipefd[1], msg, strlen(msg));//strlen 不需要加1,因为'\0'是语言层面的结束标志符,不是系统的
            sleep(1);
        }
        eixt(0);
    }
    //父进程
    close(pipefd[1]);//关闭写端,保留读端
    while(1)
    {   
    	//我们没有让父进程sleep
    	char buffer[64] = {0};
        ssize_t s = read(pipefd[0], buffer, sizeof(buffer)-1);//zero indicates end of file, 如果read的返回值是0,意味着子进程关闭了文件描述符
        if(s == 0)
        {	
        	printf("read quit...\n");
            break;
        }
        else if(s > 0)
        {
            buffer[s] = 0;
            printf("child say to father# %s\n", buffer);         
        }
        else
        {
        	printf("read error...\n");
            break;
        }
    }
    return 0;
}

上面这段代码的意思就是创建一个匿名管道,再创建一个子进程,让子进程每隔1秒就往管道里面写数据,父进程不挺从管道里面读数据
运行代码:
在这里插入图片描述
如果我们让子进程不要sleep,一直往管道里面写数据,父进程每隔一秒从管道里读取一次数据,buffer的大小还是64

运行代码后:
在这里插入图片描述
不是应该一句一句的读取吗?为什么一次读取这么多呢?
对于写端来说,pipe里面只要有缓冲区,就会一直写入。对于读端来说,缓冲区只要有数据,就会一直读取(每次都读63个字节)。
对于这种特性,就叫做字节流

如果子进程写入,而父进程就是不读

//子进程
int count = 0;
while(1)
{
     write(pipefd[1], "a", 1);
     count++;
     printf("count: %d\n", count);//写一个字符(一字节),就打印一下
}
//父进程
while(1)
{
     sleep(1);
}

运行代码:
在这里插入图片描述
最终我们发现写满了65536字节就不会写入了。65536字节也就是64KB,所以管道的大小是64KB
当写端写满的时候,为什么不写了?
因为要让读端来读。此时可能会有人问,为什么要让读端来读?如果管道满了,不等读端读取的话,再写入,也就相当于把之前写入的数据给覆盖了,那对于写端来讲,之前做的工作就白做了。进程间通信也是为了相互合作的,写入的数据就应该给读端来读,这才是相互合作的表现。

//父进程
while(1)
{   
    sleep(5);
    char c[64] = {0};
    ssize_t s = read(pipefd[0], &c, 63);
    c[s] = '\0';
    printf("father take 63 byte\n");
}

如果我们让父进程一次读取63个字节,每次读完后就打印一次数据
运行代码:
在这里插入图片描述
虽然父进程一次读63个字节,但是子进程依旧没有写入

那就让父进程每次读取2KB

//父进程

while(1)
{   
     //我们没有让父进程sleep
     sleep(5);
     char c[1024*2+1] = {0};
     ssize_t s = read(pipefd[0], &c, sizeof(c));
     c[s] = '\0';
     printf("father take 2KB \n");
}

运行代码:
在这里插入图片描述
我们发现每次读取4KB之后,写端才会写入。为什么会这样呢?原因就是要保证读写的原子性!

如果让子进程每隔4秒,写一个字符串

//子进程
while(1)
{
    write(pipefd[1], msg, strlen(msg));
    sleep(4);
}
//父进程
while(1)
{   
    char c[64] = {0};
    ssize_t s = read(pipefd[0], &c, sizeof(c));
    c[s] = '\0';
    printf("father take %s\n", c);
}

运行代码:
在这里插入图片描述
虽然读端一直在读,但是写端每隔4秒才写入一次数据,就相当于读端一直在等写端写入数据

子进程写入一条消息后,间隔7秒就退出,再关闭写端的文件描述符,而父进程一直读取

//子进程
while(1)
{
     write(pipefd[1], msg, strlen(msg));
     sleep(7);
     break;

}
close(pipefd[1]);
//父进程
while(1)
{   
     //我们没有让父进程sleep
     //sleep(5);
     char c[64] = {0};
     ssize_t s = read(pipefd[0], &c, sizeof(c));
     if(s > 0)
     {
         c[s] = '\0';
         printf("father take %s\n", c);
     }
     else if(s == 0)
     {
         printf("writer quit...\n");
         break;
     }
     else
         break;

运行代码:
在这里插入图片描述
经过测试我们发现,写端(子进程)退出后,紧接着读端(父进程)也退出了。

如果子进程不断地写入数据,而父进程先sleep10秒,然后只读一次数据,就关闭读端

//子进程
const char* msg = "hello fl";
while(1)
{
	write(pipefd[1], msg, strlen(msg));
}
//父进程
while(1)
{
	sleep(10);
	char c[64] = {0};
    ssize_t s = read(pipefd[0], &c, sizeof(c));
    break;
}
close(pipefd[0]);

我们可以用这段命令来监控一下while :; do ps axj | grep pipe_process | grep -v grep; sleep 1; echo "#################################";done
运行代码:
在这里插入图片描述
此时我们发现读端关闭,写端也会关闭。为什么呢?
当我们的读端关闭,写端还在写入,此时站在OS的层面,合理吗?
比如老师在讲课,学生都走了,都已经没人听老师讲课了
这种情况是严重不合理的,是没有任何价值的,站在OS层面,就是在浪费OS的资源,所以OS会直接终止写入进程!OS会给目标进程发送SIGPIPE信号

小总结:
匿名管道的4种情况

  1. 读端不读或者读得慢,写端要等待读端
  2. 读端关闭,写端会被关闭
  3. 写端不写或者写得慢,读端要等写端
  4. 写端关闭,读端读完数据后,并不会关闭

匿名管道的5个特点:

  1. 管道是一个只能单向通信的通信信道
  2. 管道是面向字节流的
  3. 具有血缘关系的进程进行进程间通信,例如父子进程
  4. 管道自带同步机制,原子性写入(互斥)
  5. 匿名管道是一块内核缓冲区,不会在文件系统存在,如果匿名管道对应的所有进程都关闭了,操作系统就会做相应的回收。所以管道的声生命周期是随进程的

1.2 命名管道

为了解决匿名管道只能具有血缘关系的进程进行通信,引入的命名管道。
命名管道与匿名管道的特点基本一致。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件,在文件系统中存在,生命周期随操作系统
在这里插入图片描述
第一个参数是创建管道的名字
第二个参数是创建管道的权限

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>

#define MY_FIFO "./fifo"
int main()
{
    if(mkfifo(MY_FIFO, 0666) < 0)
    {
        perror("mkfifo");
        return 1;
    }
    return 0;
}

运行代码:
在这里插入图片描述
我们看到确实创建了一个管道,但是给的权限是666,创建出来的管道为什么是664呢?
其实原因就是创建的时候是要受系统umask的影响的,所以只需要在创建管道的时候,把umask设置为0即可。
在这里插入图片描述
一旦我们具有了一个命名管道,此时,我们只需要让通信双方按文件操作即可!

看以下代码:

comm.h
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#define MY_FIFO "./fifo"

server.c
#include "comm.h"
int main()
{
    umask(0);
    if(mkfifo(MY_FIFO, 0666) < 0)
    {
        perror("mkfifo");
        return 1;
    }
    
    //只需要文件操作即可,语言层面的文件操作也行,但是可能会受到缓冲区的影响
    int fd = open(MY_FIFO, O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 2;
    }

    //业务逻辑,可以进行对应的读写
    while(1)
    {
        char buffer[64] = {0};
        ssize_t s = read(fd, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            //success
            buffer[s] = 0;
            printf("client## %s\n", buffer);
        }
        else if(s == 0)
        {
            //perr close
            printf("client quit...\n");
            break;
        }
        else
        {
            //error
            perror("read\n");
            break;
        }
    }
    close(fd);
    return 0;
}

client.c
#include "comm.h"
#include<string.h>

int main()
{
    //如果管道已经存在,就不需要创建管道
    int fd = open(MY_FIFO, O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    //业务逻辑
    while(1)
    {
        printf("请输入# ");
        fflush(stdout);
        char buffer[64] = {0};
        //先把数据从标准输入拿到我们的client进程内部
        ssize_t s = read(0, buffer, sizeof(buffer)-1);//键盘输入的时候,/n也是输入字符的一部分
        if(s > 0)
        {
            buffer[s-1] = 0;
            printf("%s\n", buffer);
            
            //拿到了数据
            write(fd, buffer, strlen(buffer));
        }
    }

    close(fd);
    return 0;
}

运行代码:
在这里插入图片描述
一个server.c进程,一个client.c进程,两个进程没有任何关系,通过命名管道就能彼此通信

我们把server.c的代码稍作修改

if(s > 0)
{
     //success
     buffer[s] = 0;
     if(strcmp(buffer, "show") == 0)
     {
         if(fork() == 0)
         {
              execl("usr/bin/ls", "ls", "-1", NULL);
              exit(1);
         }
          waitpid(-1, NULL, 0);
     }
     else if(strcmp(buffer, "run") == 0)
     {
         if(fork() == 0)
         {
              execl("/usr/bin/sl", "sl", NULL);
              exit(1);
         }
         waitpid(-1, NULL, 0);
     }
     else
         printf("client# %s\n", buffer);
            
}

运行代码:
在这里插入图片描述
此时就通过cilent就能控制server的操作了

3、共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
在这里插入图片描述
1.通过某种调用,在内存中创建一份内存空间!
2.通过某种调用,让进程"挂接"到这份新开辟的内存空降上!
通过上述这两步操作后,我们就可以使不同的进程看到了同一份资源,就能使不同的进程进行通信了,这种通信方案就叫做共享内存

2.1 shmget函数

在这里插入图片描述
功能:用来创建共享内存
参数

  • key:这个共享内存段名字
  • size:共享内存大小
  • shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

对于shmflg,在这里只说两个IPC_CREAT 和 IPC_EXCL
在这里插入图片描述
IPC_CREAT:单独使用,或者shmflg为0,则表示如果不存在为key的共享内存,就会直接创建,如果存在了,则直接返回当前已经存在的共享内存(基本不会空手而归)。
IPC_EXCL:单独使用没有意义
IPC_CREAT | IPC_EXCL:如果不存在为key的共享内存,则创建。反之则报错。(意义:如果我调用成功,得到的一定是一个最新的,没有被别人使用过的共享内存!)

对于key:
相当于唯一标识符ID,需要用户自己填入。理论来讲,用户可以随便填什么值,具体是几并不重要,重要的是它和其他key不一样。但难免会填写的值与其他的key冲突,所以我们一般使用ftok()函数获取key
在这里插入图片描述
参数

  • 自定义路径名
  • 自定义项目ID

返回值:如果是-1,则表示失败

server.c
int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        return 1;
    }
    printf("%u\n", key);
    return 0;
}

在这里插入图片描述
此时就形成了一个key为1711342188的共享内存,此时这段代码在client.c中也要有一份,因为要保证两个进程看到的是同一份共享内存,才能进行后续的通信

再看这样一段代码:

comm.h
#pragma once
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATH_NAME "./"
#define PROJ_ID 0X6666
#define SIZE 4097

server.c
#include "comm.h"

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        return 1;
    }
    int shmid = shmget(key, SIZE, IPC_CREAT|IPC_EXCL);
    if(shmid < 0)
    {
        perror("shmget");
        return 2;
    }
    printf("key: %u, shmid: %d\n", key, shmid);
    return 0;
}

运行代码:
在这里插入图片描述
第一次执行server之后,成功打印了key和shimd,我们发现shimd默认是从0开始的

除了第一次执行server之外,后面执行的server为什么会打印"shmget:file exists"呢?
此时说明共享内存已经被创建出来了。
通过指令ipcs -m可以查看被创建出来的共享内存
在这里插入图片描述

通过这个例子我们可以知道,对于该进程曾经创建的共享内存在进程结束的时候被释放了吗?答案是没有。原因是:对于systemV的IPC资源,它不属于任何一个进程,生命周期是随内核的,如果程序员不显示的释放,那么内核存在多长时间,它就存在多长时间

key vs shimd
key:只是用来在系统层面上进行标识唯一性的,不能用来管理shm
shmid:是OS给用户返回的id,用来在用户层进行shm管理

buf的类型是一个描述共享内存的数据结构,我们可以看一看这个结构里面有什么
在这里插入图片描述

2.2 shmctl函数

在这里插入图片描述
功能:用于控制共享内存
参数

  • shmid:由shmget返回的共享内存标识码
  • cmd:将要采取的动作(有三个可取值)
  • buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0;失败返回-1

comm.c
#pragma once

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATH_NAME "./"
#define PROJ_ID 0X6666
#define SIZE 4097

server.c
#include "comm.h"

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        return 1;
    }
    int shmid = shmget(key, SIZE, IPC_CREAT|IPC_EXCL);
    if(shmid < 0)
    {
        perror("shmget");
        return 2;
    }
    printf("key: %u, shmid: %d\n", key, shmid);

    sleep(5);
    shmctl(shmid, IPC_RMID, NULL);
    printf("key: 0x%x, shmid: %d->shm delete success\n", key, shmid);
    sleep(5);
    return 0;
}

运行代码:

在这里插入图片描述

2.3 shmat函数

在这里插入图片描述
功能:将共享内存段连接到进程地址空间
参数

  • shmid: 共享内存标识
  • shmaddr:指定连接的地址
  • shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存第一个字节,就是共享内存的起始地址(这个地址是虚拟地址);失败返回-1
注意:shmaddr为NULL,OS自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        return 1;
    }
    int shmid = shmget(key, SIZE, IPC_CREAT|IPC_EXCL|0666);
    if(shmid < 0)
    {
        perror("shmget");
        return 2;
    }
    printf("key: %u, shmid: %d\n", key, shmid);
    sleep(5);

    char* mem = (char*)shmat(shmid, NULL, 0);
    printf("attaches shm success\n");
    sleep(5);

    //这里就是我们的通信逻辑
    
    shmctl(shmid, IPC_RMID, NULL);
    printf("key: 0x%x, shmid: %d->shm delete success\n", key, shmid);
    return 0;
}

运行代码:
在这里插入图片描述
我们发现nattch(有多少个进程与之相关联)最终有0变为了1,表明已经将该进程挂接到了shmid为4的共享内存上了

2.4 shmdt函数

在这里插入图片描述
功能:将共享内存段与当前进程脱离
参数

  • shmaddr: 由shmat所返回的指针(共享内存的起始地址)

    返回值:成功返回0;失败返回-1
    注意:将共享内存段与当前进程脱离不等于删除共享内存段

#include "comm.h"

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        return 1;
    }
    int shmid = shmget(key, SIZE, IPC_CREAT|IPC_EXCL|0666);
    if(shmid < 0)
    {
        perror("shmget");
        return 2;
    }
    printf("key: %u, shmid: %d\n", key, shmid);
    sleep(5);

    char* mem = (char*)shmat(shmid, NULL, 0);
    printf("attaches shm success\n");
    sleep(5);

    //这里就是我们的通信逻辑

    shmdt(mem);
    printf("detaches shm success\n");
    sleep(5);
    
    shmctl(shmid, IPC_RMID, NULL);
    printf("key: 0x%x, shmid: %d->shm delete success\n", key, shmid);
    return 0;
}

运行代码:
在这里插入图片描述
我们发现nattch先是由0变为了1,再由1变为了0,说明该进程先挂接到了共享内存上,然后又取消了挂接

再看这样一段代码:

comm.h
#pragma once

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

#define PATH_NAME "./"
#define PROJ_ID 0X6666
#define SIZE 4097

client.c
#include "comm.h"

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        return 1;
    }
    printf("%u\n", key);

    //client这里只需要获取共享内存即可,因为它在server端已经被创建了
    int shmid = shmget(key, SIZE, IPC_CREAT);
    if(shmid < 0)
    {
        perror("shmget");
        return 1;
    }

    char* mem = (char*)shmat(shmid, NULL, 0);
    sleep(5);
    printf("client process attaches success!\n");


    //这个地方就是我们要通信的区域
    shmdt(mem);
    sleep(5);
    printf("client process detaches success!\n");

    //client要不要删除共享内存呢? 不需要
    return 0;
}

srerver.c
#include "comm.h"

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        return 1;
    }
    int shmid = shmget(key, SIZE, IPC_CREAT|IPC_EXCL|0666);
    if(shmid < 0)
    {
        perror("shmget");
        return 2;
    }
    printf("key: %u, shmid: %d\n", key, shmid);
    sleep(1);

    char* mem = (char*)shmat(shmid, NULL, 0);
    printf("attaches shm success\n");
    sleep(15);

    //这里就是我们的通信逻辑

    shmdt(mem);
    printf("detaches shm success\n");
    sleep(5);
    
    shmctl(shmid, IPC_RMID, NULL);
    printf("key: 0x%x, shmid: %d->shm delete success\n", key, shmid);
    return 0;
}

改代码的意思就是先让server端创建一个共享内存,自己再挂接到共享内存上,client端再挂接到共享内存上,然后client端取消挂接,server端再取消挂接。我们将看到nattch的变化过程为0->1->2->1->0
在这里插入图片描述

重点:

1.共享内存一旦建立好并映射进自己的进程地址空间,该进程就可以直接看到该共享内存,就如同malloc的空间一样,不需要任何系统调用接口
2.共享内存不提供任何同步或者互斥机制,需要程序员自行保证数据的安全性
3.共享内存是进程之间通信最快的方式,因为它省略了若干次数据拷贝,例如从用户到内核和内核到用户

4、消息队列

消息队列是消息的链表,是存放在内核中并由消息队列标识符标识。因此是随内核持续的,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区受限等特点。允许不同进程将格式化的数据流以消息队列形式发送给任意进程,对消息队列具有操作权限的进程都可以使用msgget完成对消息队列的操作控制,通过使用消息类型,进程可以按顺序读信息,或为消息安排优先级顺序。

与信号量相比,都以内核对象确保多进程访问同一消息队列。但消息队列发送实际数据,信号量进行进程同步控制。

与管道相比,管道发送的数据没有类型,读取数据端无差别从管道中按照前后顺序读取;消息队列有类型,读端可以根据数据类型读取特定的数据。

操作:创建或获取消息队列, int msgget((key_tkey, int flag);//若存在获取,否则创建它

发送消息:int msgsnd(int msgid, void *ptr, size_t size, int flag); ptr指向一个结构体存放类型和数据 size 数据的大小

接受消息:int msgrcv(int msgid, void *ptr, size_t size, long type, int flag);

删除消息队列: int msgctl(int msgid, int cmd, struct msgid_ds*buff);

5、信号量

管道,共享内存,消息队列,它们都是以传输数据为目的的!
信号量不是以传输数据为目的!它是通过共享"资源"的方式,来达到多个进程的同步和互斥的目的!
信号量的本质:是一个计数器,类似int count。衡量临界资源中的资源数

1.什么是临界资源?

凡是被多个执行流同时能够访问的资源就是临界资源,例如进程间通信时,使用的管道、共享内存、消息队列都是临界资源。凡是需要进程通信,必定要引入多个进程看到的资源(通信需要),同时,也引入了一个新的问题,那就是临界资源

2.什么是临界区?

用来访问临街资源的代码就叫做临界区

3.什么是原子性?

任何情况下不能被打断的操作,也就是说要么都执行,要么不执行,没有中间态

4.什么是互斥?

在任意一个时刻,只能允许一个执行流进入临界资源,执行它自己的临界区

信号量的先关操作:

int semget((key_t)key, int nsems, int flag);//创建或获取信号量

int semop(int semid, stuct sembuf*buf, int length);//加一操作(V操作):释放资源;减一操作(P操作):获取资源

int semct(int semid, int pos, int cmd);//初始化和删除
  • 24
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值