Linux进程间通信(个人笔记)

news2024/10/10 14:48:16

Linux进程通信

  • 1.进程通信介绍
    • 1.1进程间通信目的
    • 1.2进程间通信发展
    • 1.3进程间通信的具体分类
  • 2.管道
    • 2.1匿名管道
      • 2.1.1代码实例
      • 2.1.2 fork共享管道原理
      • 2.1.3 管道的读写规则与特点
      • 2.1.4 进程池
    • 2.2 命名管道
      • 2.2.1 命名管道的创建
      • 2.2.2匿名管道与命名管道的区别
      • 2.2.3代码实例
  • 3.System V共享内存
    • 3.1 共享内存数据结构
    • 3.2 共享内存函数接口
    • 3.3 共享内存代码实例
  • 4.System V消息队列
  • 5.System V信号量


1.进程通信介绍

1.1进程间通信目的

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

1.2进程间通信发展

管道-》System V进程间通信-》POSIX进程间通信

1.3进程间通信的具体分类

管道:
1.匿名管道pipe
2.命名管道

System V IPC:
1.System V 消息队列
2.System V 共享内存
3.System V 信号量

POSIX IPC
1.消息队列
2.共享内存
3.信号量
4.互斥量
5.条件变量
6.读写锁

2.管道

管道是Unix中最古老的进程间通信方式
从一个进程连接到另外一个进程的一个数据流称作一个管道
举例

who | wc -l

在这里插入图片描述

who命令显示当前登录的用户及其相关信息
| 是管道符,表示将 who 命令的输出传递给下一个命令
wc -l 命令计算输入的行数
然而整个命令的作用是输出当前登录用户的总数。

2.1匿名管道

#include<unistd.h>
//功能创建一个无名管道
int pipe(int fd[2]);
//参数
//fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写段
//返回值:成功返回0,失败返回错误代码

在这里插入图片描述

2.1.1代码实例

#include <iostream>
#include <cassert>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAX 1024
using namespace std;

// a.管道的4种情况
//    1.正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
//    2.正常情况,如果管道被写满了,写段必须等待,直到有空间为止(读端读走数据)
//    3.写段关闭,读端一直读取,读端会读到read返回值为0,表示读到文件末尾
//    4.读端关闭,写段一直写入,os会杀掉写端进程,通过向目标进程发送SIGPIPE(13)信号,终止目标进程
// b.管道的五种特性
//    1.匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用于父子,仅限于此
//    2.匿名管道,默认给读写端提供同步机制
//    3.面向字节流
//    4.管道的生命周期是随进程的
//    5.管道是单向通信的,半双工通信的一种特殊情况

int main()
{
  // 第一步:创建管道
  int pipefd[2];
  int n = pipe(pipefd);
  assert(n == 0);
  (void)n; // 防止编译器告警,意料之中用assert,意料之外用if

  cout << "pipefd[0]:" << pipefd[0] << ",pipefd[1]:" << pipefd[1] << endl;

  // 第二步:创建子进程
  pid_t id = fork();
  if (id < 0)
  {
    perror("fork");
    return 1;
  }
  // 子写,父读
  // 第三步:父子关闭不需要的fd,形成单向通信的管道
  if (id == 0)
  {
    // if(fork()>0) exit(0);//这里是父孙进程可以进行通信
    // child
    close(pipefd[0]);
    // w-只向管道写入,没有打印
    int cnt = 0;
    while (true)
    {
      // 这里是测试管道文件大小是多少,得出的结果是64KB
      // char c='a';
      // write(pipefd[1],&c,1);
      // cnt++;
      // cout<<"write....:"<<cnt<<endl;

      char message[MAX];
      snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
      cnt++;
      write(pipefd[1], &message, strlen(message));
      sleep(1);
    }
    cout << "child close w piont" << endl;
    // close(pipefd[1]);//进程退出会自动关闭文件描述符
    exit(0);
  }
  close(pipefd[1]);

  // r
  char buffer[MAX];
  while (true)
  {
    ssize_t n = read(pipefd[0], buffer, strlen(buffer) - 1);
    if (n > 0)
    {
      buffer[n] = 0; // ‘\0’,当作字符串
      cout << getpid() << ", " << "child say: " << buffer << "to me!" << endl;
    }
    else if (n == 0)
    {
      cout << "child quit, me too !" << endl;
      break;
    }
    cout << "father return val(n): " << n << endl;
    sleep(1);
    break;
  }
  cout << "read point close" << endl;
  close(pipefd[0]);

  sleep(5);
  int status = 0;

  pid_t rid = waitpid(id, &status, 0);
  if (rid == id)
  {
    cout << "wait success,child exit sig: " << (status & 0x7F) << endl;
  }
  return 0;
}

2.1.2 fork共享管道原理

在这里插入图片描述
在这里插入图片描述

2.1.3 管道的读写规则与特点

a.管道的4种情况
1.正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
2.正常情况,如果管道被写满了,写段必须等待,直到有空间为止(读端读走数据)
3.写段关闭,读端一直读取,读端会读到read返回值为0,表示读到文件末尾
4.读端关闭,写段一直写入,os会杀掉写端进程,通过向目标进程发送SIGPIPE(13)信号,终止目标进程
b.管道的五种特性
1.匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用于父子,仅限于此
2.匿名管道,默认给读写端提供同步机制
3.面向字节流
4.管道的生命周期是随进程的
5.管道是单向通信的,半双工通信的一种特殊情况

2.1.4 进程池

在这里插入图片描述

//ProcessPool.cc
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"

const int num = 5;
static int number = 1;

class channel
{
public:
  channel(int fd, pid_t id) : ctrlfd(fd), workerid(id)
  {
    name = "channel-" + std::to_string(number++);
  }

public:
  int ctrlfd;
  pid_t workerid;
  std::string name;
};

void Work()
{
  while (true)
  {
    int code = 0;
    while (true)
    {
      int code = 0;
      ssize_t n = read(0, &code, sizeof(code));
      if (n == sizeof(code))
      {
        if (!init.CheckSafe(code))
        {
          continue;
        }
        init.RunTask(code);
      }
      else if (n == 0) // 这里是写端退出
      {
        break;
      }
      else
      {
        // 这里是出错处理暂不处理
        // do nothing
      }
    }
  }
  std::cout << "child quit" << std::endl;
}

void PrintFd(const std::vector<int> &fds)
{
  std::cout << getpid() << "close fds: ";
  for (auto fd : fds)
  {
    std::cout << fd << " ";
  }
  std::cout << std::endl;
}

// 传参形式:
// 1.输入函数:const &
// 2.输出参数: *
// 3.输入输出参数:&

void CreateChannels(std::vector<channel> *c)
{
  std::vector<int> old;
  for (int i = 0; i < num; i++)
  {
    // 1.定义并创建管道
    int pipefd[2];
    int n = pipe(pipefd);
    assert(n == 0);
    (void)n;

    // 2.创建进程
    pid_t id = fork();
    assert(id != -1);

    // 3.构建单向通信信道
    if (id == 0)
    {
      if (!old.empty())
      {
        for (auto fd : old)
        {
          close(fd);
        }
        PrintFd(old);
      }
      close(pipefd[1]);
      dup2(pipefd[0], 0);
      Work();
      exit(0); // 会自动关闭自己打开的所有的Fd
    }

    // father
    close(pipefd[0]);
    c->push_back(channel(pipefd[1], id));
    old.push_back(pipefd[1]);
    // childid,pipefd[1]
  }
}

void PrintDebug(const std::vector<channel> &c)
{
  for (const auto &channel : c)
  {
    std::cout << channel.name << ", " << channel.ctrlfd << ", " << channel.workerid << std::endl;
  }
}

void SendCommand(const std::vector<channel> &c, bool flag, int num = -1)
{
  int pos = 0;
  while (true)
  {
    // 1.选择任务
    int command = init.SelectTask();

    // 2.选择信道(进程)
    const auto &channel = c[pos++];
    pos %= c.size();

    // debug
    std::cout << "send command " << init.ToDesc(command) << "[" << command << "]"
              << " in "
              << channel.name << " worker is : " << channel.workerid << std::endl;

    // 3.发送任务
    write(channel.ctrlfd, &command, sizeof(command));

    // 4.判断是否要退出
    if (!flag)
    {
      num--;
      if (num <= 0)
      {
        break;
      }
    }
    sleep(1);
  }
  std::cout << "SendCommand done..." << std::endl;
}

void ReleaseChannels(std::vector<channel> c)
{

  // version 2
  // int num = c.size() - 1;

  // for (; num >= 0; num--)
  // {
  //     close(c[num].ctrlfd);
  //     waitpid(c[num].workerid, nullptr, 0);
  // }

  // version 1
  for (const auto &channel : c)
  {
    close(channel.ctrlfd);
    waitpid(channel.workerid, nullptr, 0);
  }
  // for (const auto &channel : c)
  // {
  //     pid_t rid = waitpid(channel.workerid, nullptr, 0);
  //     if (rid == channel.workerid)
  //     {
  //         std::cout << "wait child: " << channel.workerid << " success" << std::endl;
  //     }
  // }
}

int main()
{
  std::vector<channel> channels;
  // 1.创建信道,创建进程
  CreateChannels(&channels);

  // 2.开始发送任务
  const bool g_alway_loop = true;
  // SendCommand(channels,g_alway_loop);
  SendCommand(channels, !g_alway_loop, 10);

  // 3.回收资源,想让子进程退出,并且释放管道,只要关闭写端
  ReleaseChannels(channels);

  return 0;
}
//Task.hpp
#pragma once

#include <iostream>
#include <functional>
#include <vector>
#include <ctime>
#include <unistd.h>

// using task_t =std::function<void()>;
typedef std::function<void()> task_t;

void Download()
{
  std::cout << "我是一个下载任务" << "处理者:" << getpid() << std::endl;
}

void PrintLog()
{
  std::cout << "我是一个打印日志的任务" << "处理者:" << getpid() << std::endl;
}

void PushVideoStream()
{
  std::cout << "这是一个推送视频流的任务" << "处理者" << getpid() << std::endl;
}

class Init
{
public:
  // 任务码
  const static int g_download_code = 0;
  const static int g_printlog_code = 1;
  const static int g_push_videostream_code = 2;

  // 任务集合
  std::vector<task_t> tasks;

public:
  Init()
  {
    tasks.push_back(Download);
    tasks.push_back(PrintLog);
    tasks.push_back(PushVideoStream);

    srand(time(nullptr) ^ getpid());
  }

  bool CheckSafe(int code)
  {
    if (code >= 0 && code < tasks.size())
    {
      return true;
    }
    else
    {
      return false;
    }
  }

  void RunTask(int code)
  {
    return tasks[code]();
  }

  int SelectTask()
  {
    return rand() % tasks.size();
  }

  std::string ToDesc(int code)
  {
    switch (code)
    {
    case g_download_code:
      return "Download";
    case g_printlog_code:
      return "PrintLog";
    case g_push_videostream_code:
      return "PushVideoStream";
    default:
      return "Unknow";
    }
  }
};

Init init;

在这里插入图片描述
注意循环创建信道时,子进程的文件描述符表拷贝父进程的,会导致信道被多个文件描述符指向,在释放文件描述符的时候要尤为注意,下面把代码单独拧出来,以便思考

void CreateChannels(std::vector<channel> *c)
{
  std::vector<int> old;
  for (int i = 0; i < num; i++)
  {
    // 1.定义并创建管道
    int pipefd[2];
    int n = pipe(pipefd);
    assert(n == 0);
    (void)n;

    // 2.创建进程
    pid_t id = fork();
    assert(id != -1);

    // 3.构建单向通信信道
    if (id == 0)
    {
      if (!old.empty())
      {
        for (auto fd : old)
        {
          close(fd);
        }
        PrintFd(old);
      }
      close(pipefd[1]);
      dup2(pipefd[0], 0);
      Work();
      exit(0); // 会自动关闭自己打开的所有的Fd
    }

    // father
    close(pipefd[0]);
    c->push_back(channel(pipefd[1], id));
    old.push_back(pipefd[1]);
    // childid,pipefd[1]
  }
}

void ReleaseChannels(std::vector<channel> c)
{

  // version 2
  // int num = c.size() - 1;

  // for (; num >= 0; num--)
  // {
  //     close(c[num].ctrlfd);
  //     waitpid(c[num].workerid, nullptr, 0);
  // }

  // version 1
  for (const auto &channel : c)
  {
    close(channel.ctrlfd);
    waitpid(channel.workerid, nullptr, 0);
  }
  // for (const auto &channel : c)
  // {
  //     pid_t rid = waitpid(channel.workerid, nullptr, 0);
  //     if (rid == channel.workerid)
  //     {
  //         std::cout << "wait child: " << channel.workerid << " success" << std::endl;
  //     }
  // }
}

2.2 命名管道

匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件

2.2.1 命名管道的创建

1.命名管道可以从命令行上创建

mkfifo filename

2.命名管道从程序中创建

int mkfifo(const char* filename,mode_t mode);

2.2.2匿名管道与命名管道的区别

匿名管道由pipe函数创建并打开
命名管道由mkfifo函数创建,打开用open
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,他们具有相同的语义

2.2.3代码实例

//comm.h
#pragma once

#define FILENAME "fifo"
//Makefile
.PHONY:all
all:server cilent


server:server.cc
  g++ -o $@ $^ -std=c++11
cilent:cilent.cc
  g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
  rm -f server cilent fifo
//server.cc
#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.h"

bool MakeFifo()
{
  int fd = mkfifo(FILENAME, 0666);
  if (fd < 0)
  {
    std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
    return false;
  }
  std::cout << "mkfifo success... read" << std::endl;
  return true;
}

int main()
{
Start:
  int rfd = open(FILENAME, O_RDONLY);
  if (rfd < 0)
  {
    std::cerr << "errno: " << errno << ",errstring: " << strerror(errno) << std::endl;
    if (MakeFifo())
    {
      goto Start;
    }
    else
    {
      return 1;
    }
  }
  std::cout << "open fifo success..." << std::endl;

  char buffer[1024];
  while (true)
  {
    ssize_t s = read(rfd, &buffer, sizeof(buffer) - 1);
    if (s > 0)
    {
      buffer[s] = 0;
      std::cout << "Client say# " << buffer << std::endl;
    }
    else if (s == 0)
    {
      std::cout << "Client quit, server quit too!" << std::endl;
      break;
    }
  }

  close(rfd);
  std::cout << "close fifo sucess..." << std::endl;
  return 0;
}
//cilent.cc
#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.h"

int main()
{
  int wfd = open(FILENAME, O_WRONLY);
  if (wfd < 0)
  {
    std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
    return 1;
  }
  std::string message;
  while (true)
  {
    std::cout << "Please Enter# ";
    std::getline(std::cin, message);
    ssize_t s = write(wfd, message.c_str(), message.size());
    if (s < 0)
    {
      std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
      break;
    }
  }
  close(wfd);
  std::cout << "close fifo success..." << std::endl;
  return 0;
}

3.System V共享内存

在这里插入图片描述
进程通信的前提:必须让不同的进程看到同一份资源(必须由OS提供)
OS会允许系统中同时存在多个共享内存,先描述,在组织,对共享内存进行管理,进程间是通过一个提前约定好的标识看到同一个共享内存的

3.1 共享内存数据结构

在这里插入图片描述

3.2 共享内存函数接口

shmget函数
功能:用来创建共享内存
原型:int shmget(key_t key,size_t size,int shmflg);
参数:
key:提前约定好的一个钥匙,通常是路径加数字转换而来的
size:共享内存大小
shmflg:用法和创建文件使用的mode模式标识是一样的
返回值:
成功返回一个非负整数,即共享内存的标识码;失败返回-1
在这里插入图片描述

shmat函数
功能:将共享内存段连接到进程地址空间
原型:void* shmat(int shmid,const void* shmaddr,int shmflg);
参数:
shmid:共享内存标识
shmaddr:指定连接的地址
shmflg:两个取值:SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指针指向共享内存首地址;失败返回-1
shmaddr为NULL,核心自动选择一个地址

shmdt函数
功能:将共享内存段与当前进程脱离
原型:int shmdt(const void* shmaddr);
参数:shmaddr:由shmat所返回的指针
返回值:成功返回0,失败返回-1
注意:将共享内存段与当前进程脱离,但不等于删除共享内存段

shmctl函数
功能:用于控制共享内存
原型:int shmctl(int shmid,int cmd,struct shmid_ds* buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(三个取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

命令说明
IPC_STAT把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID删除共享内存段

3.3 共享内存代码实例

//Makefile
.PHONY:all
all:server client
server:server.cc
  g++ -o $@ $^ -std=c++11
client:client.cc
  g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
  rm -f server client fifo

//comm.hpp
#pragma once
#include <iostream>
#include <cstdlib>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>

const std::string pathname = "/home/whb/pipe_fifo_shm";
const int proj_id = 0x11223344;

const std::string filename = "fifo";

// 共享内存的大小,强烈建议设置为n*4096
const int size = 4096;

key_t GetKey()
{
  key_t key = ftok(pathname.c_str(), proj_id);
  if (key < 0)
  {
    std::cerr << "errno: " << errno << ",errstring: " << strerror(errno) << std::endl;
    exit(1);
  }
  return key;
}

std::string ToHex(int id)
{
  char buffer[1024];
  snprintf(buffer, sizeof(buffer), "0x%x", id);
  return buffer;
}

int CreateShmHelper(key_t key, int flag)
{
  int shmid = shmget(key, size, flag);
  if (shmid < 0)
  {
    std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
    exit(2);
  }
  return shmid;
}

int CreateShm(key_t key)
{
  return CreateShmHelper(key, IPC_CREAT | IPC_EXCL | 0644);
}

int GetShm(key_t key)
{
  return CreateShmHelper(key, IPC_CREAT);
}

bool MakeFifo()
{
  int n = mkfifo(filename.c_str(), 0666);
  if (n < 0)
  {
    std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
    return false;
  }

  std::cout << "mafifo success... read" << std::endl;
  return true;
}
//server.cc
#include <iostream>
#include <cstring>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include "comm.hpp"

class Init
{
public:
  Init()
  {
    // bool r=MakeFifo();
    // if(!r)
    //     return;
    key_t key = GetKey();
    std::cout << "key : " << ToHex(key) << std::endl;
    // sleep(3);
    // key vs shmid
    // shmid:应用这个共享内存的时候,我们使用shmid来进行操作共享内存,  FILE*
    // key:不要在应用层使用,只用来在内核中标识shm的唯一性!,         fd
    shmid = CreateShm(key);
    std::cout << "shmid: " << shmid << std::endl;

    // sleep(10);
    std::cout << "开始将shm映射到进程的地址空间中" << std::endl;

    s = (char *)shmat(shmid, nullptr, 0);
    // fd=open(filename.c_str(),O_RDONLY);
  }

  ~Init()
  {
    // sleep(5);
    shmdt(s);
    std::cout << "开始将shm从进程地址空间中移除" << std::endl;

    // sleep(5);
    shmctl(shmid, IPC_RMID, nullptr);
    std::cout << "开始将shm从os中删除" << std::endl;

    // close(fd);
    // unlink(filename.c_str());
  }

public:
  int shmid;
  int fd;
  char *s;
};

int main()
{
  key_t key = GetKey();
  // int msgid = msgget(key, IPC_CREAT | IPC_EXCL);
  // std::cout << "msgid: " << msgid << std::endl;
  // struct msqid_ds ds;
  // msgctl(msgid, IPC_STAT, &ds);
  // std::cout << ds.msg_qbytes << std::endl;
  // std::cout << ToHex(ds.msg_perm.__key) << std::endl;
  // sleep(10);

  // int semid = semget(key, 1, IPC_CREAT | IPC_EXCL);
  // std::cout << "semid: " << semid << std::endl;

  // sleep(4);

  // semctl(semid, 1, IPC_RMID);

  // msgctl(msgid,IPC_RMID,nullptr);

  Init init;
  struct shmid_ds ds;
  shmctl(init.shmid, IPC_STAT, &ds);

  std::cout << ToHex(ds.shm_perm.__key) << std::endl;
  std::cout << ds.shm_segsz << std::endl;
  std::cout << ds.shm_segsz << std::endl;
  std::cout << ds.shm_segsz << std::endl;
  std::cout << ds.shm_atime << std::endl;
  std::cout << ds.shm_nattch << std::endl;

  sleep(5);

  // TODO
  while (true)
  {
    // wait
    int code = 0;
    ssize_t n = read(init.fd, &code, sizeof(code));
    if (n > 0)
    {
      // 直接读取
      std::cout << "共享内存的内容: " << init.s << std::endl;
      sleep(1);
    }
    else if (n == 0)
    {
      break;
    }
  }

  sleep(10);
  return 0;
}
//client.cc
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "comm.hpp"

int main()
{
  key_t key = GetKey();
  int shmid = GetShm(key);
  char *s = (char *)shmat(shmid, nullptr, 0);
  std::cout << "attach shm done" << std::endl;
  int fd = open(filename.c_str(), O_WRONLY);

  // sleep(10);
  // TODO
  // 共享内存的通信方式,不会提供同步机制,共享内存是直接裸露给所有使用者的,一定要注意共享内存的使用安全问题
  //
  char c = 'a';
  for (; c <= 'z'; c++)
  {
    s[c - 'a'] = c;
    std::cout << "write : " << c << "done" << std::endl;
    sleep(1);

    // 通知对方
    int code = 1;
    write(fd, &code, sizeof(4));
  }
  shmdt(s);
  std::cout << "detach shm done" << std::endl;
  close(fd);
  return 0;
}

在这里插入图片描述

在这里插入图片描述

4.System V消息队列

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

特性方面
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核
在这里插入图片描述

5.System V信号量

信号量的本质是一个计数器
为了让进程间通信-》多个执行流看到的同一份资源,公共资源-》并发访问-》数据不一致的问题-》保护起来-》互斥和同步
互斥:任何一个时刻只允许一个执行流(进程)访问公共资源,加锁完成
同步:多个执行流执行的时候,按照一定的顺序执行
被保护起来的公共资源,临界资源
访问该临界资源的代码,我们叫做临界区
而维护临界资源,其实就是维护临界区
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2202286.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot洗衣店订单系统:数据驱动的决策

3系统分析 3.1可行性分析 通过对本洗衣店订单管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本洗衣店订单管理系统采用JAVA作为开发语言&#xff0c;S…

如何安装Llama3.1 —— 附一键安装包!

一键安装包文末领取&#xff01; 下载地址 软件&#xff1a;Llama版本&#xff1a;3.1语言&#xff1a;简体中文大小&#xff1a;645 MB安装环境&#xff1a;Win10及以上版本(64bit) 软件简介 ‌‌LLaMA模型是Meta研发的大语言模型‌&#xff0c;旨在帮助研究人员推进他们…

vue-jsonp的使用和腾讯地图当前经纬度和位置详情的获取

1.下载&#xff1a; npm install –save vue-jsonp2.main.js中引入&#xff1a; //腾讯逆地址解析会用到jsonp import {VueJsonp} from vue-jsonp; Vue.use(VueJsonp);3.腾讯地图中使用 uniapp中获取*经纬度*和通过经纬度获取当前**位置详情** //获取当前经纬度 getLocation…

【AI系统】AI在不同领域的应用与行业影响

本文将探讨AI在不同技术领域和行业中的广泛应用&#xff0c;以及这些应用如何影响和改变我们的世界。 I. 引言 AI技术正日益渗透到各个技术领域&#xff0c;从计算机视觉到自然语言处理&#xff0c;再到音频处理&#xff0c;AI的应用正变得越来越广泛。这些技术的发展不仅推动…

影刀RPA实战:制作Excel工资条

1.实战目标 使用Excel制作工资条是一种常见的做法&#xff0c;每个公司几乎都有这样的需求&#xff0c;我们先介绍下使用excel手动制作工资条的方法&#xff0c;看看不足之处&#xff0c;使用影刀RPA又会给我们带来怎样的便利&#xff0c;让我们更倾向于选择影刀来处理。 工资…

世昌股份与吉利亲密关系:资产负债率远高同行,应收账款周转率偏弱

《港湾商业观察》杨丹妮 9月23日&#xff0c;河北世昌汽车部件股份有限公司&#xff08;以下简称“世昌股份”&#xff09;回复了第一轮审核问询函&#xff0c;公司于今年六月递表北交所&#xff0c;保荐机构为东北证券。 近几年新能源汽车如日中天&#xff0c;世昌股份也因此…

系统端口号被占用问题处理(WindowsLinux系统)

Windows 直接kill占用端口的进程 WinR 输入cmd 打开命令行窗口 1.查询本地已被占用的端口号&#xff1a; 下面以8080端口为例&#xff1a; netstat -aon|findstr "8080" 查看本地8080端口进程的PID 2.杀死"xxxx"端口号的进程 (下面的22868是 你查到…

DAY5 数组

概念 数组是一个数据容器&#xff0c;可用来存储一批同类型的数据。 存储原理 int[] arr new int[]{12,24,36}; 理解为数组对象中存储的为地址信息 一维数组 静态数组 数据类型[] 数组名 new 数据类型[]{元素1,元素2,......}&#xff1b; 数据类型[] 数组名 {元素1,元…

惯导+卫导组合高精度模块UM981系列新品特点

近日&#xff0c;和芯星通发布了UM981系列全系统全频高精度RTK/INS组合定位模块&#xff0c;可同时跟踪 BDS B1I/B2I/B3I/B1C/B2a/B2b&#xff0c;GPS L1/L2/L5&#xff0c;GLONASS G1/G2/G3&#xff0c;Galileo E1/E5a/E5b/E6&#xff0c;QZSS L1/L2/L5&#xff0c;NavIC L5&a…

Extreme Compression of Large Language Models via Additive Quantization阅读

文章目录 Abstract1. Introduction2. Background & Related Work2.1. LLM量化2.2. 最近邻搜索的量化 3.AQLM:Additive Quantization for LLMs3.1. 概述3.1.0 补充**步骤说明****举例说明** 3.2. 阶段1&#xff1a;代码的波束搜索3.3. 阶段2&#xff1a;码本更新3.4. 阶段3&…

Java中对象的比较(equals、Comparable、Comparator)

文章目录 一、PriorityQueue中插入对象二、元素的比较 2.1、基本类型的比较2.2、对象比较的问题三、对象的比较 3.1、覆写基类的equals3.2、基于Comparable接口类的比较3.3、基于比较器比较3.4、三种方式对比 一、PriorityQueue中插入对象 前篇我们讲解了优先级队列&#xff0…

mpi 示例小程序集锦

1&#xff0c;小ring 程序 数据从rank 0开始向外传递&#xff0c;rank1收到后再传递给 rank2&#xff0c;以此类推 除了rank0&#xff0c;程序一开始时&#xff0c;每个rank先进入接收等待状态&#xff0c;像是多米诺骨牌&#xff0c;都立起来。 rank 0开始倒下&#xff0c;依…

设备管理系统:强化特种设备安全监管的智能引擎

设备管理系统在特种设备安全监管中发挥着至关重要的作用&#xff0c;主要体现在以下几个方面&#xff1a; 一、实时监控与预警 运行状态监控&#xff1a;设备管理系统能够实时监控特种设备的运行状态&#xff0c;包括开机时间、运行时间、停机时间等关键参数。这有助于及时发现…

初级网络工程师之从入门到入狱(六)

本文是我在学习过程中记录学习的点点滴滴&#xff0c;目的是为了学完之后巩固一下顺便也和大家分享一下&#xff0c;日后忘记了也可以方便快速的复习。 网络工程师从入门到入狱 前言一、vlan与vlanif二、路由器之三层通信方式2.1、路由器物理接口2.2、子接口2.3、vlanif 三、数…

Tars简介

定义&#xff1a;Tars协议的高性能rpc开发框架 功能&#xff1a;可扩展协议编解码、高性能rpc通信框架、名字路由和发现、发布监控、日志统计、配置管理 整体架构 交互流程 1.服务发布 2.管理命令 3.心跳上报 4.信息上报 server服务运行后&#xff0c;会定期上报统计信息到s…

高中毕业|转行AI产品经理经验都在这了

从高中毕业以后第一份客服工作➡️设计师➡️产品➡️AI产品&#xff0c;从月薪4k到年薪22w&#xff0c;我觉得在转行这方面我太有发言权了&#xff0c;所以今天为大家整理我的转行经验&#xff0c;希望能帮助到大家&#xff01; 找工作&#xff0c;大家胆子一定要大&#xff0…

10月10日微语报,星期四,农历九月初八

10月10日微语报&#xff0c;星期四&#xff0c;农历九月初八&#xff0c;工作愉快&#xff0c;生活喜乐&#xff01; 一份微语报&#xff0c;众览天下事&#xff01; 1、从严处置&#xff01;网信部门曝光“毒视频”“开盒挂人”等涉未成年人乱象。 2、A股新纪录诞生&#x…

Windows 下纯手工打造 QT 开发环境

用过 QtCreator 和 VS QT 插件&#xff0c;都觉得不是很理想。所以有了这个想法。 手工打造的 QT 的开发环境&#xff0c;是不需要安装上面两个程序的。 1、下载 vcpkg&#xff0c;编译 QT6 下载地址&#xff1a;https://github.com/microsoft/vcpkg.git 进入到 …

mac本地VSCode配置LeetCode

1、安装node.js及环境配置 https://www.jb51.net/article/283302.htm mac配置后如何找到Node地址 打开终端&#xff0c;输入which node 2、安装leetcode插件 https://zhuanlan.zhihu.com/p/488602193 3、mac配置力扣插件 https://juejin.cn/post/7050349897756704805 4、登…

前瞻性的索引设计

目录 介绍两个快速、易用的技术&#xff1a;基本问题法&#xff08;BQ&#xff09;和快速上限估算法&#xff08;QUBE&#xff09; 发现不合适的索引 基本问题法 对每个SELECT语句&#xff0c;以下问题的答案都必须按下述步骤来考虑 如何确定一个方案能否让SELECT在最差输…