Liinux——进程间通信之共享内存与信号量

news2024/11/30 3:28:39

进程间通信之共享内存与信号量

在这里插入图片描述

System V进程间通信

system V 进程通信是一组在 Unix 和类 Unix 系统中用于进程间通信的机制,主要三种方式:共享内存、消息队列与信号量

今天我们一起来对共享内存进行详细的学习,并了解信号量的基本概念

1. 共享内存 shm

我们常说:进程间通信的本质实际上就是让不同进程看到同一份资源。在之前,我们学习了基于文件的进程间通信(管道),今天,我们来学习基于内存的进程间通信:

1.1 共享内存的基本实现逻辑

我们可以在物理内存申请一块空间,然后让两个不同的进程通过页表的映射将这个相同的物理内存映射到自己的虚拟地址空间,这样,这两个不同的进程就看到相同的内存资源了。如图:

在这里插入图片描述

同时我们也应该清楚,在操作系统中,肯定有很多对进程都在使用共享内存进程通信,因此共享内存绝对不止一个,所以操作系统肯定要对这些共享内存做管理,如何管理:

先描述,再组织:将描述共享内存的各种属性(如编号、大小)封装成内核数据结构,这样,操作系统对共享内存的管理就变成了对共享内存内核数据结构的增删查改

此外,由于共享内存在内存中不只存在一个,那么,如何实现让通信双方打开的是同一个共享内存呢

  • 给共享内存提供唯一的标识符key
  • 通信双方在打开共享内存通信前就要获得共享内存的key

关于如何获取key,创建、查看、操作、删除共享内存,以及如何用共享内存进行通信,就是我们接下来要讨论的话题:

1.2 共享内存的创建

通过系统调用来创建共享内存:

 #include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
  • key:即共享内存在内核中的唯一标识符

  • size申请的共享内存的大小(单位为Byte)

    • 需要注意:操作系统给共享内存分配空间时,是以4096字节(4KB)为单位进行分配的。如果用户定义的size为4000字节,那么尽管系统给共享内存分配了4096字节,但是自己只能使用4000字节,相当于浪费了96字节
    • 因此,size的大小应该是4096字节(4KB)的整数倍
  • shmflg操作选项,利用位图进行操作:

    • IPC_CREAT如果共享内存不存在,就创建,存在就返回
    • IPC_EXCL:单独使用没意义
    • IPC_CREAT | IPC_EXCL如果共享内存不存在,就创建,存在就出错返回
    • 权限值
  • 返回值

  • 如果成功,就返回共享内存的唯一标识符shmid

  • 如果失败,就返回-1

1.2.1 key的获取

通过系统调用来获取key

#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

这个系统调用通过用户给定的pathnameproj_id,利用特殊的算法得到唯一的key值

  • pathname:这个文件应该是存在且可访问,同时应该尽量稳定,不会被轻易删除或移动
  • proj_id:是一个非零整数
  • 返回值
    • 如果成功,则返回key
    • 如果失败,则返回-1

看到这里,大家可能会有一个疑惑:既然key是共享内存在内核中的唯一标识符,那为什么不由操作系统自动创建,而要由用户自己调用系统调用来创建呢

首先我们要清楚,两个进程要通过共享内存进行通信,首先要打开相同的共享内存,而为了确保打开的是相同的共享内存,就需要在通信前这两个进程就要获得相同的key

如果key由操作系统自动创建,那么就做不到在通信前就将这个key发送给两个进程(OS怎么知道这两个进程要通信了)

而如果key由用户通过系统调用自己创建,那么双方就可以提前约定好pathnameproj_id,从而生成相同的key,这样就可以在通信时根据相同的key来打开相同的共享内存,从而进行通信了

1.3 共享内存的查看

通过命令查看系统当前存在的共享内存:

ipcs -m

例如:

#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string>
#include <cstring>
#include <unistd.h>

//将key转换成16进制
std::string toHex(key_t key)
{
    char buffer[100] = {0};
    snprintf(buffer, sizeof(buffer) - 1, "0x%x", key);

    return buffer;
}

int main()
{
    key_t key = ftok(".", 1);
    if (-1 == key)
    {
        printf("get key error: %s", strerror(errno));
        exit(1);
    }
    std::cout << "key: " << toHex(key) << std::endl;

    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL);	//创建共享内存,如果存在就出错返回
    if (-1 == shmid)
    {
        printf("get shm error: %s", strerror(errno));
        exit(2);
    }
    std::cout << "shmid: " << shmid << std::endl;

    std::cout << "process will exit......" << std::endl;
    sleep(5);

    return 0;
}

运行并查看共享内存:

在这里插入图片描述

从中,我们可以看到一个现象:通过ipcm -m查看,发现虽然进程已经退出了,但是由这个进程创建的共享内存任然存在。这说明:

和管道不一样,共享内存是内核级的,并不会随着进程的退出而销毁

接下来我们来分析通过ipcs -m 能看到共性内存的信息:

在这里插入图片描述

  • key:即共享内存内核中的唯一标识符

  • shmid:共享内存的唯一标识符

  • owner:共性内存的拥有者

  • perms:共享内存的权限,如果要修改共享内存的权限,在shmget的参数shmflg后加上权限即可,例如:

    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666); //设置共享内存的权限为666
    
  • nattch:挂载到该共享内存的进程个数

  • status:共享内存的状态

1.3.1 key和shmid

看到这里,可能又有小伙伴会有疑惑:key和shmid都是共享内存的唯一标识符,二者之间有什么不同吗

事实上,尽管二者都是共享内存shm的唯一标识符,但二者之间的用途却不同:

  • key:是内核级别的唯一标识符,即操作系统识别共享内存的唯一标识符
  • shmid:是用户层面的唯一标识符,之后**通过系统调用操控共享内存时,用的都是shmid**而不是key

1.4 共享内存的挂载/取消挂载

前面我们说过,要利用共享内存进行通信,仅仅在物理内存申请一块内存空间是不够的,我们还需要通过操作将这块空间通过页表映射到通信进程的虚拟地址空间,这一过程就叫做挂载

通过系统调用进行挂载/取消挂载:

#include <sys/shm.h>

void *shmat(int shmid, const void *_Nullable shmaddr, int shmflg);
int shmdt(const void *shmaddr);
  • shmid:共享内存的唯一标识符
  • shmaddr:填nullptr即可
  • shmflg:填0即可
  • 返回值:
    • 如果成功,就返回共享内存被映射到虚拟地址空间的起始地址
    • 如果失败,就返回(void*)-1

int shmdt(const void *shmaddr);:即从调用进程的地址空间中分离位于 shmaddr 指定地址的共享内存段。

1.5 共享内存的操控

利用系统调用来操控共享内存:

#include <sys/shm.h>

int shmctl(int shmid, int op, struct shmid_ds *buf);
  • shmid:共享内存的唯一标识符
  • op:要进行操作的选项
  • buf:一个指向shmid_ds结构体的指针,结构体shmid_ds定义在<sys/shm.h
  • 返回值:失败返回-1

例如:

#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string>
#include <cstring>
#include <unistd.h>

//将key转换成16进制
std::string toHex(key_t key)
{
    char buffer[100] = {0};
    snprintf(buffer, sizeof(buffer) - 1, "0x%x", key);

    return buffer;
}
int main()
{
    key_t key = ftok(".", 1);
    std::cout << "key: " << toHex(key) << std::endl;

    int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
    std::cout << "shmid: " << shmid << std::endl;

    std::cout << "shm create success\n" << std::endl;

    //获取共享内存的信息
    struct shmid_ds shmidDs;
    int ret = shmctl(shmid, IPC_STAT, &shmidDs);        
    printf("key: %d, nattch: %ld\n",shmidDs.shm_perm.__key, shmidDs.shm_nattch);

    //挂载共享内存
    char* buffer = (char*)shmat(shmid, nullptr, 0); 
    if ((void*)-1 != buffer)
        std::cout << "shm attach success" << std::endl;

    std::cout << "shm will detattch" << std::endl;
    sleep(5);

    //取消挂载共享内存
    ret = shmdt(buffer);
    if (0 == ret)
        std::cout << "shm detattch success" << std::endl;

    sleep(3);

    //删除共享内存
    ret = shmctl(shmid, IPC_RMID, nullptr);     
    std::cout << "shm delete success" << std::endl;

    std::cout << "process exit" << std::endl;

    return 0;
}

运行结果:

在这里插入图片描述

除了通过系统调用shmctl(shmid, IPC_RMID, nullptr),同样可以通过命令来删除共享内存

ipcrm -m [shmid]

1.6 利用共享内存实现进程间的通信

代码如下:

common.hpp

#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>

#define PATHNAME "."
#define PROJ_ID 1
#define SIZE 4096
#define MODE 0666

int _create_shm(key_t key, size_t size, int shmflg)
{
    int shmid = shmget(key, size, shmflg);
    return shmid;
}

std::string toHex(key_t key)
{
    char buffer[1024] = {0};
    snprintf(buffer, sizeof(buffer) - 1, "0x%x", key);
    return buffer;
}

key_t get_key(const char* pathname = PATHNAME, int proj_id = PROJ_ID)
{
    size_t key = ftok(pathname, proj_id);
    if (-1 == key)
    {
        std::cerr << "get_key error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(1);
    }

    std::cout << "get_key success, key: " << toHex(key) << std::endl;
    return key;
}

//用于服务端:
//创建共享内存,如果已经存在,就出错返回
int create_shm(key_t key, size_t size)
{
    int shmid = _create_shm(key, size, IPC_CREAT | IPC_EXCL | MODE);
    if (-1 == shmid)
    {
        std::cerr << "shmid error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(2);
    }
    std::cout << "create shm success, shmid: " << shmid << std::endl;

    return shmid;
}

//用于客户端
//获取服务端已经创建好的共享内存
int get_shm(key_t key, size_t size)
{
    int shmid = _create_shm(key, size, IPC_CREAT);
    if (-1 == shmid)
    {
        std::cerr << "get_shm error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(3);
    }

    std::cout << "get shm success, shmid: " << shmid << std::endl;
    return shmid;
}

void delete_shm(int shmid)
{
    int ret = shmctl(shmid, IPC_RMID, nullptr);
    if (-1 == ret)
    {
        std::cerr << "delete_shm error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(4);
    }

    std::cout << "delete shm success" << std::endl;
}

void* attach_shm(int shmid)
{
    void* address = shmat(shmid, nullptr, 0);
    if (address == (void*)-1)
    {
        std::cerr << "aattch_shm error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(5);
    }

    std::cout << "aattch shm success" << std::endl;
    return address;
}

int disattach_shm(void* address)
{
    int ret = shmdt(address);
    if (-1 == ret)
    {
        std::cerr << "disattach_shm error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(6);
    }
    std::cout << "disattach shm success" << std::endl;

    return 0;
}

shm_server.cc

#include "common.hpp"

int main()
{
    std::cout << "I am server" << std::endl;

    //获取key
    key_t key = get_key();

    //创建共享内存
    int shmid = create_shm(key, SIZE);

    //挂载共享内存
    char* buffer = (char*)attach_shm(shmid);

    std::cout<<std::endl;
    
    while(1)
    {
        std::cout << "client message: " << buffer << std::endl;
        sleep(1);
    }

    //取消挂载
    disattach_shm((void*)buffer);

    sleep(5);

    //删除共享内存
    delete_shm(shmid);
    return 0;
}

shm_client

#include "common.hpp"

int main()
{
    std::cout << "I am client" << std::endl;

    //获取key
    key_t key = get_key();

    //创建共享内存
    int shmid = get_shm(key, SIZE);

    //挂载共享内存
    char* buffer = (char*)attach_shm(shmid);
    
    //开始通信 write
    for (char ch = 'A'; ch <= 'Z'; ch++)
    {
        buffer[ch - 'A'] = ch;
        sleep(1);
    }

    //取消挂载
    disattach_shm((void*)buffer);

    sleep(5);

    return 0;
}

结果如图:

在这里插入图片描述

从运行的结果我们可以看出:服务端(读端)还没等客户端写,就已经开始读取共享内存的数据,客户端(写)都已经推出了,服务端(读)还没有推出。

从中我们可以得出结论:

共享内存这种进程间通信的方式并不提供同步机制(写端还没写或着还没写完,读端就要阻塞等待),这可能会导致数据错乱与不一致

为了避免出现数据错乱,不一致的问题,需要用户来实现通信的同步机制,其中一种方法就是使用管道来实现(管道天生就是同步的)

重写代码如下:

fifo.hpp

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define PATH "FIFO"
#define MODE 0666

class fifo
{
public:
    fifo(const std::string& name)
    : _name(name)
    {
        int ret = mkfifo(_name.c_str(), MODE);
        if (-1 == ret)
        {
            std::cerr << "mkfifo error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;
            exit(-1);
        }
        std::cout << "fifo made success" << std::endl;
    }
    ~fifo()
    {
        unlink(_name.c_str());
    }
private:
    const std::string _name;
};

common.hpp

#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
#include "fifo.hpp" 

#define PATHNAME "."
#define PROJ_ID 1
#define SIZE 4096
#define MODE 0666

int _create_shm(key_t key, size_t size, int shmflg)
{
    int shmid = shmget(key, size, shmflg);
    return shmid;
}

std::string toHex(key_t key)
{
    char buffer[1024] = {0};
    snprintf(buffer, sizeof(buffer) - 1, "0x%x", key);
    return buffer;
}

key_t get_key(const char* pathname = PATHNAME, int proj_id = PROJ_ID)
{
    size_t key = ftok(pathname, proj_id);
    if (-1 == key)
    {
        std::cerr << "get_key error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(1);
    }

    std::cout << "get_key success, key: " << toHex(key) << std::endl;
    return key;
}

//用于服务端:
//创建共享内存,如果已经存在,就出错返回
int create_shm(key_t key, size_t size)
{
    int shmid = _create_shm(key, size, IPC_CREAT | IPC_EXCL | MODE);
    if (-1 == shmid)
    {
        std::cerr << "shmid error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(2);
    }
    std::cout << "create shm success, shmid: " << shmid << std::endl;

    return shmid;
}

//用于客户端
//获取服务端已经创建好的共享内存
int get_shm(key_t key, size_t size)
{
    int shmid = _create_shm(key, size, IPC_CREAT);
    if (-1 == shmid)
    {
        std::cerr << "get_shm error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(3);
    }

    std::cout << "get shm success, shmid: " << shmid << std::endl;
    return shmid;
}

void delete_shm(int shmid)
{
    int ret = shmctl(shmid, IPC_RMID, nullptr);
    if (-1 == ret)
    {
        std::cerr << "delete_shm error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(4);
    }

    std::cout << "delete shm success" << std::endl;
}

void* attach_shm(int shmid)
{
    void* address = shmat(shmid, nullptr, 0);
    if (address == (void*)-1)
    {
        std::cerr << "aattch_shm error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(5);
    }

    std::cout << "aattch shm success" << std::endl;
    return address;
}

int disattach_shm(void* address)
{
    int ret = shmdt(address);
    if (-1 == ret)
    {
        std::cerr << "disattach_shm error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
        exit(6);
    }
    std::cout << "disattach shm success" << std::endl;

    return 0;
}

//用于实现同步机制
class Syc
{
public:
    //用于读端sever,等待写端的唤醒
    bool wait_message()
    {
        _rfd = open(PATH, O_RDONLY);
        if (_rfd < 0)
        {
            std::cerr << "open rfd error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
            exit(7);
        }
		
        int code = 0;
        int n = read(_rfd, &code, sizeof(code));
        if (n == sizeof(code))	//如果收到了client端发来的唤醒码,就说明写端已经写完了,读端被唤醒
        {
            std::cout << "server weak up" << std::endl;
            return true;
        }
        else if (n == 0)
        {
            return false;
        }
        else
        {
            std::cerr << "read error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
            exit(8);
        }
    }
	
    //用于写端client,用于唤醒读端server
    void weak_up()
    {
        int _wfd = open(PATH, O_WRONLY);
        if (_wfd < 0)
        {
            std::cerr << "open wfd error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
            exit(9);
        }
		
        //向读端server发送唤醒码
        int code = 0;
        int n = write(_wfd, &code, sizeof(code));
        if (n != sizeof(code))
        {
            std::cerr << "write error, " << "errno: " << errno << ", errnostring: " << strerror(errno) << std::endl;
            exit(10);
        }
    }
private:
    int _rfd = 0;
    int _wfd = 0;
};

shm_server

#include "common.hpp"

int main()
{
    std::cout << "I am server" << std::endl;

    //获取key
    key_t key = get_key();

    //创建共享内存
    int shmid = create_shm(key, SIZE);

    //挂载共享内存
    char* buffer = (char*)attach_shm(shmid);

    std::cout<<std::endl;

    //开始通信 read
    //引入命名管道,实现同步
    fifo named_pipe(PATH);
    Syc syc;

    while(1)
    {
        //如果成功被唤醒,就输出写端信息
        if (syc.wait_message())
        {
            std::cout << "client message: " << buffer << std::endl;
        }
        else
            break;
    }

    //取消挂载
    disattach_shm((void*)buffer);

    sleep(5);

    //删除共享内存
    delete_shm(shmid);
    return 0;
}

shm_client

#include "common.hpp"

int main()
{
    std::cout << "I am client" << std::endl;

    //获取key
    key_t key = get_key();

    //创建共享内存
    int shmid = get_shm(key, SIZE);

    //挂载共享内存
    char* buffer = (char*)attach_shm(shmid);
    
    //开始通信 write
    //引入命名管道,实现同步
    Syc syc;
    while(1)
    {
        std::cout << "client message #";
        memset(buffer, 0, sizeof(buffer));
        fgets(buffer, 4095, stdin);

        if (std::string(buffer) == "quit\n")
            break;
        
        syc.weak_up();	//写端写完,唤醒读端
        sleep(1);
    }

    //取消挂载
    disattach_shm((void*)buffer);

    sleep(5);

    return 0;
}

结果如图:

在这里插入图片描述

1.7 共享内存的优缺点

缺点:

共享内存不提供通信的同步机制,这可能会导致数据的不一致问题

优点:

共享内存是所有进程间通信方式中最快的

  • 因为其直接将共享内存通过页表映射到了自己的虚拟地址空间,可以直接对其进行读和写,而不需要频繁地使用系统调用read()、write(),从而节省了时间开销

2. 信号量(信号灯) sem

在学习信号量之前,我们先来明确几个概念:

同步与互斥

对共享资源进行保护,是一个多执行流场景下,一个比较常见和重要的话题(例如共享内存),保护的方式一般分为两种:

  • 同步:对于同一份资源,允许不同进程在安全的前提下,以一定的顺序进行访问
  • 互斥:对于同一份资源,任何时候都只允许一方来进行访问

原子性

原子性是指:一个操作只有两种状态,要么已经执行完毕,要么还没有执行。不存在执行中的状态

临界资源与临界区

临界资源:被保护起来的,任何时刻只允许一个执行访问流访问的公共资源

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

从程序员的角度来看,对临界资源的保护其实就是对访问临界资源的代码即临界区的保护

2.1 信号量的基本概念

信号量的本质是对资源的预定机制

  • 对于某一份资源,不一定要我持有,才是我的
  • 只要我预定了这份资源,那在未来的某一段时间,这段资源就一定会被我持有
  • 被我预定的资源,只允许我访问,不会被并发访问。除非我主动释放,才允许其他人访问

假设一个场景:

对于一块较大的资源,如果我们将其当作一个整体,那么当要访问这块资源进行读写操作时,由于要确保数据的一致性和稳定性,一般只同时允许一个进程读,一个进程写,这样对这块资源的利用率显然不高

为了提高资源利用率,操作系统提供了这样的办法:可以将一块较大的资源分成许多小块,这样就允许多对进程对这些小块资源进行访问,从而也就提高了资源的利用率,但是这里也面临了两个问题:

  1. 如何控制访问资源的进程的数量
  2. 如何合理分配资源?

而信号量就是用来解决第一个问题的:

信号量实际上就是一个计数器,用来描述一块临界资源可以被访问的进程数量

  • 如果一个进程要访问这块资源,就要申请信号量,如果成功,代表获得了这块资源的访问权限,同时信号量进行--操作
  • 如果一个进程要停止访问这块资源,就要释放信号量,此时信号量就要进行++操作

注:如果一个信号量的值只能取0/1,那么就相当于只允许一个进程对这块临界资源进行访问,其他资源都要等待,这就实现了互斥

2.2 信号量的实现

看到这里,可能有小伙伴会想:既然信号量实际上就是一个计数器,那么我是不是可以直接在代码里用一个计数器count来实现?

答案当然是不可以!!!

原因有两点:

  1. 写时拷贝
  • 首先我们要清楚:如果有多个进程要访问同一块临界资源,且这块临界资源被一个信号量所管理,那么为了确保数据的安全性,这些进程就要看到这个信号量,即:这些进程看到了信号量这一公共的资源
  • 如果有用普通的代码计数器来当作信号量,那么当一个进程申请信号量时,就要对信号量(计数器)进行--操作,而由于这个计数器是多个进程共享的资源,那么当一个进程对其进行修改时,就会发生写时拷贝,这就会导致其他的进程的计数器并不会被影响
  1. 代码计数器count++、——操作不是原子性的
  • 前面就说过,原子性指的是一个操作,要么已经执行完毕,要么还没执行。代码的++操作一般分为三步(--同理):读取当前的值、对读取的值进行+1操作,最后返回。这是一个过程,并不符合原子性的条件
  • 信号量的++、--操作必须是原子性的,这样才能确保数据的统一

因此,信号量的++、--操作都交给操作系统来实现,一般,我们将信号量的--操作称为P操作,++操作称为V操作,统称信号量的PV操作

2.3 查看、获取信号量

通过命令来查看信号量:

ipcs -s

通过系统调用来获取信号量

#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
  • key:信号量集合的键值,可由ftok()获取
  • nsems:要创建的信号量的个数
  • semlfg:标志参数
    • IPC_CREAT如果共享内存不存在,就创建,存在就返回
    • IPC_EXCL:单独使用没意义
    • IPC_CREAT | IPC_EXCL如果共享内存不存在,就创建,存在就出错返回
    • 权限值
  • 返回值:
    • 成功:返回信号量集和标识符
    • 失败:返回-1

2.4 控制信号量

通过系统调用来控制信号量

#include <sys/sem.h>

int semctl(int semid, int semnum, int op, ...);
  • semid:信号量集合标识符

  • semnum:要控制哪一个信号量(信号量集和下标从0开始

  • op:操作选项

    • IPC_RMID:删除信号量集合
    • ………………

如果要删除信号量,也可以通过命令ipcrm -s [semid]删除指定信号量集和

2.5 信号量的PV操作

通过系统调用来对信号量进行PV操作

#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, size_t nsops);
  • semid:信号量结合标识符

  • nsops:要对哪一个信号量进行操作(下标)

  • sops:一个结构体,定义了操作方法,其包含的内容如下:

    unsigned short sem_num;  /* semaphore number 及上面的nsops */
    short          sem_op;   /* semaphore operation  P操作为-1 V操作为1*/
    short          sem_flg;  /* operation flags 0即可*/
    
  • 返回值:失败返回-1


本篇完

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

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

相关文章

阿里发布 EchoMimicV2 :从数字脸扩展到数字人 可以通过图片+音频生成半身动画视频

EchoMimicV2 是由阿里蚂蚁集团推出的开源数字人项目&#xff0c;旨在生成高质量的数字人半身动画视频。以下是该项目的简介&#xff1a; 主要功能&#xff1a; 音频驱动的动画生成&#xff1a;EchoMimicV2 能够使用音频剪辑驱动人物的面部表情和身体动作&#xff0c;实现音频与…

node.js nvm 安装和使用

个人笔记记录。 参考文档&#xff1a;https://blog.csdn.net/weixin_45811256/article/details/130860444 1、下载nvm-setup.exe 安装程序 2、将本地的node卸载&#xff0c;然后点击进行安装。 3、安装 node.js 方法一&#xff1a; 去nodejs官网搜索历史版本&#xff0c;找…

js:函数

函数 函数&#xff1a;实现抽取封装&#xff0c;执行特定任务的代码块&#xff0c;方便复用 声明 函数命名规范 尽量小驼峰 前缀应该为动词&#xff0c;如getName、hasName 函数的调用 函数体是函数的构成部分 函数传参 参数列表里的参数叫形参&#xff0c;实际上写的数据叫实…

【大模型】基于LLaMA-Factory的模型高效微调

LLaMA-Factory项目介绍 LLaMA Factory 是一个简单易用且高效的大型语言模型&#xff08;Large Language Model&#xff09;训练与微调平台。通过 LLaMA Factory&#xff0c;可以在无需编写任何代码的前提下&#xff0c;在本地完成上百种预训练模型的微调&#xff0c;框架特性包…

论文笔记 SliceGPT: Compress Large Language Models By Deleting Rows And Columns

欲买桂花同载酒&#xff0c;终不似&#xff0c;少年游。 数学知识 秩&#xff1a; 矩阵中最大线性无关的行/列向量数。行秩与列秩相等。 线性无关&#xff1a;对于N个向量而言&#xff0c;如果任取一个向量 v \textbf{v} v&#xff0c;不能被剩下的N-1个向量通过线性组合的方式…

hadoop_zookeeper详解

Zookeeper秒懂 工作机制特点数据结构应用场景安装选举机制初始化启动无法和Leader保持连接 节点类型监听器原理写数据流程Paxos算法算法流程 客户端命令 Zookeeper 是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的 Apache 项目。 工作机制 Zookeeper是一个基于观察…

MD5算法加密笔记

MD5是常见的摘要算法。 摘要算法&#xff1a; 是指把任意⻓度的输⼊消息数据转化为固定⻓度的输出数据的⼀种密码算法. 摘要算法是 不可逆的, 也就是⽆法解密. 通常⽤来检验数据的完整性的重要技术, 即对数据进⾏哈希计算然后⽐ 较摘要值, 判断是否⼀致. 常⻅的摘要算法有: MD5…

【LeetCode每日一题】——717.1比特与2比特字符

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时空频度】九【代码实现】十【提交结果】 一【题目类别】 数组 二【题目难度】 简单 三【题目编号】 717.1比特与2比特字符 四【题目描述】 有两种特…

ATTCK红队评估实战靶场(二)

http://vulnstack.qiyuanxuetang.net/vuln/?page2 描述&#xff1a;红队实战系列&#xff0c;主要以真实企业环境为实例搭建一系列靶场&#xff0c;通过练习、视频教程、博客三位一体学习。本次红队环境主要Access Token利用、WMI利用、域漏洞利用SMB relay&#xff0c;EWS re…

SpringMVC |(一)SpringMVC概述

文章目录 &#x1f4da;SpringMVC概述&#x1f407;三层架构&#x1f407;异步调用 &#x1f4da;SpringMVC入门案例&#x1f407;入门案例&#x1f407;注意事项 &#x1f4da;小结 学习来源&#xff1a;黑马程序员SSM框架教程_SpringSpringMVCMaven高级SpringBootMyBatisPlus…

uniapp实现APP版本升级

App.vue 直接上代码 <script>export default {methods: {//APP 版本升级Urlupload() {// #ifdef APP-PLUSplus.runtime.getProperty(plus.runtime.appid, (info) > {// 版本号变量持久化存储getApp().globalData.version info.version;this.ToLoadUpdate(info.versi…

visionpro官方示例分析(一) 模板匹配工具 缺陷检测工具

1.需求&#xff1a;找出图像中的这个图形。 2.步骤 使用CogPMAlignTool工具&#xff0c;该工具是模板匹配工具&#xff0c;见名知意&#xff0c;所谓模板匹配工具就是说先使用该工具对一张图像建立模板&#xff0c;然后用这个模板在其他图像上进行匹配&#xff0c;匹配上了就说…

QT:多ui界面显示

文章目录 1.多ui界面添加2.跳转函数3.返回函数4.Qt5源码工程5.模态显示 1.多ui界面添加 最终生成这个目录 2.跳转函数 void MainWindow::on_pushButton_clicked() {//this->setWindowModality(Qt::WindowModal);test1 *t1 new test1();t1->setParentData(this);this-…

python代码示例(读取excel文件,自动播放音频)

目录 python 操作excel 表结构 安装第三方库 代码 自动播放音频 介绍 安装第三方库 代码 python 操作excel 表结构 求出100班同学的平均分 安装第三方库 因为这里的表结构是.xlsx文件,需要使用openpyxl库 如果是.xls格式文件,需要使用xlrd库 pip install openpyxl /…

CSS技巧之2D转换

目录 2D转换 2D 转换之移动 translate 2D 转换之旋转 rotate 2D 转换中心点 transform-origin 2D 转换之缩放scale 2D 转换综合写法 2D转换 转换(transform)是CS53中具有颠覆性的特征之一&#xff0c;可以实现元素的位移、旋转、缩放等效果转换(lranslorm)你可以简单理解…

架构-微服务-服务治理

文章目录 前言一、服务治理介绍1. 什么是服务治理2. 常见的注册中心 二、nacos简介三、nacos实战入门1. 搭建nacos环境2. 将商品微服务注册到nacos3. 将订单微服务注册到nacos 四、实现服务调用的负载均衡1. 什么是负载均衡2. 自定义实现负载均衡3. 基于Ribbon实现负载均衡 五、…

常见的概念 及 分布式系统的演变过程

文章目录 一. 概念解释1. 应用&#xff08;Application&#xff09;/ 系统&#xff08;System&#xff09;2. 模块&#xff08;Module&#xff09;/ 组件&#xff08;Component&#xff09;3. 分布式&#xff08;Distributed&#xff09;4. 集群&#xff08;Cluster&#xff09…

探讨播客的生态系统

最近对播客发生了兴趣&#xff0c;从而引起了对播客背后的技术&#xff0c;生态的关注。本文谈谈播客背后的技术生态系统。 播客很简单 播客&#xff08;podcast&#xff09;本质上就是以语音的方式发布信息。它和博客非常类似。如果将CSDN 网站上的文字加一个语音播报。CSDN …

微信小程序WXSS全局样式与局部样式的使用教程

微信小程序WXSS全局样式与局部样式的使用教程 引言 在微信小程序的开发中,样式的设计与实现是提升用户体验的关键部分。WXSS(WeiXin Style Sheets)作为微信小程序的样式表语言,不仅支持丰富的样式功能,还能通过全局样式与局部样式的灵活运用,帮助开发者构建美观且易于维…

Apache-maven在Windows中的安装配置及Eclipse中的使用

Apache Maven 是一个自动化项目管理工具&#xff0c;用于构建&#xff0c;报告和文档的项目管理工具。以下是在不同操作系统上安装和配置 Maven 的基本步骤&#xff1a; 安装 Maven 下载 Maven: apache-maven-3.9.9下载地址&#xff0c;也可访问 Apache Maven 官方网站 下载最…