【Linux】进程间通信——system V 共享内存、消息队列、信号量

需要云服务器等云产品来学习Linux的同学可以移步/–>腾讯云<–/官网,轻量型云服务器低至112元/年,优惠多多。(联系我有折扣哦)

文章目录

  • 写在前面
  • 1. 共享内存
    • 1.1 共享内存的概念
    • 1.2 共享内存的原理
    • 1.3 共享内存的使用
      • 1.3.1 创建
      • 1.3.2 控制
      • 1.3.3 关联
      • 1.3.4 去关联
      • 1.3.6 实战——实现server和client端的通信
      • 1.3.7 共享内存的特点
  • 2. 消息队列(不常用)
    • 2.1 消息队列的概念
    • 2.2 消息队列的使用
      • 2.2.1 获取消息队列
      • 2.2.2 控制消息队列
      • 2.2.3 发送和接收数据
  • 3. 信号量
    • 3.1 信号量的概念扫盲
    • 3.2 内核中信号量的相关数据结构
    • 3.3 信号量的PV操作
    • 3.4 信号量相关函数
      • 3.4.1 申请信号量
      • 3.4.2 控制信号量
      • 3.4.3 信号量的操作PV

写在前面

在上一篇文章中我们讲了一种进程间通信的方式管道,管道通信的本质是基于文件的,也就是说OS没有为此做过多的设计,但是system V IPC是操作系统特地设置的一种通信方式。提供的通信方式有以下三种

  • system V 共享内存
  • system V 消息队列
  • system V 信号量

1. 共享内存

1.1 共享内存的概念

我们在上一篇文章中讲到:要实现进程间通信,就一定要让不同的进程看到同一份资源

在匿名管道/命名管道中,我们让不同进程看到同一份资源的方式是让不同进程打开同一个文件,使用对文件的读写来实现进程间通信,那么除此之外,我们还有其他方法

让不同进程能够使用同一块物理内存,就是共享内存的核心思想

1.2 共享内存的原理

由于进程具有独立性,内核数据结构包括对应的代码、数据和页表都是独立的,为了实现进程间通信需要以下过程:

1. OS申请一段空间

2. 将申请好的物理内存空间映射到一个进程地址空间

3. 将同一块物理内存空间映射到另一个需要通信的进程中

4. 通信结束之后取消进程和物理内存的映射关系,然后释放内存

  • 我们把OS申请的空间叫做共享内存
  • 进程和共享内存建立映射关系叫做挂接
  • 取消进程和共享内存之间的映射关系叫做去关联
  • 释放内存叫做释放共享内存

image-20240119223153670

对共享内存的理解

在C语言中,我们可以使用malloc在物理内存上申请空间,并把申请的空间经过页表映射到进程地址空间中,返回进程地址空间的指定地址,但是对于共享内存的通信方式,需要被专门设计。因为在同一时间,可能会有很多进程需要使用这种方式进行通信,所以一定会同时存在很多的共享内存,所以需要被管理起来,因此需要被专门设计

1.3 共享内存的使用

1.3.1 创建

我们使用shmget系统调用来创建共享内存

image-20240119224359785

头文件: 
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型: int shmget(key_t key, size_t size, int shmflg);
参数解释:
    key:是一个保证共享内存编号唯一性的标识符,为了让相同的进程能够看到同一个共享内存
    size:创建的共享内存的大小
    shmflg:创建共享内存的选项,通常我们使用两个:IPC_CREAT和IPC_EXCL
返回值:如果调用成功就返回一个合法的共享内存描述符shmid,如果调用失败就返回-1同时设置错误码

shmflg的选项含义:

  • IPC_CREAT:如果对应key的共享内存不存在就创建,如果存在就获取对应的shmid
  • IPC_EXCL:这个选项不能单独使用,和IPC_CREAT配合使用,如果不存在就创建,存在就出错返回

key的形成方式

我们使用一个特定的函数ftok来形成一个唯一的key

image-20240119225546733

头文件:
#include <sys/type.h>
#include <sys/ipc.h>
函数原型:
key_t ftok(const char *pathname, int proj_id);
参数解释:
	pathname:这是一个指向用于生成键值的路径名的C字符串指针。通常会选择一个已经存在的文件作为这个路径名,因为它可以确保唯一性。通常情况下,可以选择程序中的某个文件作为路径名,这样就可以确保不同的程序使用不同的路径名生成不同的键值。
	proj_id:这是一个整数值,用于进一步区分不同的 IPC 对象。这个值在给定路径名的范围内必须唯一。通常情况下,可以使用与程序相关的整数值作为 proj_id,以确保不同的程序使用不同的 proj_id 生成不同的键值。
返回值: 如果调用成功就返回对应的key值,调用失败就返回-1,同时设置错误码

对key和shmid的理解

key是在OS层面的,给OS看的标定共享内存的标识符,shmid是应用层的,是给我们看的,标定共享内存的标识。key和shmid的关系就像是inode和fd的关系

举个例子:

/*comm.hpp*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#include <cerrno>
#include <iostream>

#define PATHNAME "."  // 使用当前目录作为项目目录
#define PROJ_ID 0x66  // 随机的项目id
#define MAX_SIZE 4096 // 创建的共享内存大小

key_t getKey() // 封装获取key的函数
{
    key_t k = ftok(PATHNAME, PROJ_ID);
    if (k == -1)
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

int getShmHelper(key_t key, int flags) // 封装通过key来获取shmid的函数
{
    int shmid = shmget(key, MAX_SIZE, flags);
    if(shmid < 0)
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
        exit(2);
    }
    return shmid;
}

int getShm(key_t key) // 用于找到已经创建的共享内存的shmid,所以传入的选项只有IPC_CREAT,不关心以前是否创建
{
    return getShmHelper(key, IPC_CREAT);
}

int createShm(key_t key) // 用于创建,所以传入的选项中有IPC_EXCL,表示如果遇到冲突就创建失败
{
    return getShmHelper(key, IPC_CREAT | IPC_EXCL | 0666); // 0666表示创建的共享内存的权限
}
/*server.cc*/
#include "comm.hpp"

int main()
{
    key_t k = getKey();
    printf("key:0x%x\n", k);
    int shmid = createShm(k);
    printf("%d\n", shmid);
    return 0;
}

/*client.cc*/
#include "comm.hpp"

int main()
{
    key_t k = getKey();
    printf("key:0x%x\n", k);
    int shmid = getShm(k);
    printf("%d\n", shmid);
    return 0;
}

image-20240119234453939

1.3.2 控制

上述的代码编译出来的程序第一次运行没有任何问题,但是如果再次运行server就会发现:

image-20240119234652904

这是因为创建的共享内存没有被释放,所以我们在使用完共享内存后需要释放

补充:查看IPC资源

我们可以通过命令ipcs系列指令来查看进程间通信相关信息

image-20240120182028480

  • 删除共享内存

    ipcrm -m + shmid

    image-20240120182434821

当然除了命令删除之外,还可以使用系统调用来对共享内存进行删除/控制:shmctl

image-20240120183057685

头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数解释:
	shmid:要控制的共享内存的shmid
	cmd:要执行的命令,包括:IPC_STAT,IPC_SET,IPC_RMID,IPC_INFO,SHM_INFO,SHM_STAT,SHM_LOCK,SHM_UNLOCK.这里我们先不关注其他的,只关注IPC_RMID指令,这个指令是用来释放对应的shmid的
	buf:在其他指令中,有一些是需要获取到一些信息的,buf作为输出型参数来保存相关信息
返回值:对于释放共享内存来说,0表示成功,-1表示失败

使用shell脚本监视共享内存的情况

while :; do ipcs -m ; echo "##########################################"; sleep 1; done
#include "comm.hpp"

int main()
{
    key_t k = getKey();
    printf("key:0x%x\n", k);
    int shmid = createShm(k);
    printf("%d\n", shmid);

    sleep(5);
    shmctl(shmid, IPC_RMID, nullptr); // 这里不需要获取信息,传入nullptr即可
    return 0;
}

image-20240120184045003

1.3.3 关联

在本节开始,我们说过有一个过程叫做把物理内存和进程地址空间关联起来,我们会使用一个系统调用关联:shmat,这里的at取attach的意义

image-20240120184534528

头文件:
#include <sys/types.h>
#include <sys/shm.h>
函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数解释:
	shmid:需要关联的shmid
	shmaddr:关联到进程地址空间的地址,我们绝大多数时间是不指定的,所以传nullptr即可
	shmflg:关联的选项,默认为0,表示读写权限
返回值:
	关联成功返回共享内存映射到进程地址空间中的起始地址,失败返回-1并设置错误码
void *attachShm(int shmid)
{
    void *mem = shmat(shmid, nullptr, 0);
    if((long long)mem == -1L) // 这里由于我们的机器是64位的,所以一个地址占8个字节,所以需要转成long long类型判断是否正确关联
    {
        std::cout << errno << " : " << strerror(errno) << std::endl;
        exit(3);
    }
    return mem;
}

1.3.4 去关联

有关联,那么对应的就有去关联的操作,去关联使用的系统调用是shmdt

image-20240120185829638

头文件:
#include <sys/types.h>
#include <sys/shm.h>
函数原型:
int shmdt(const void *shmaddr);
参数解释:
	shmaddr:需要去关联的进程地址空间
返回值:
	如果调用成功就返回0,否则就返回-1,同时设置错误码
void detachShm(void* start)
{
    if(shmdt(start) == -1)
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
    }
}

1.3.6 实战——实现server和client端的通信

/*comm.hpp*/
#pragma once
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <iostream>

#define PATHNAME "."  // 使用当前目录作为项目目录
#define PROJ_ID 0x66  // 随机的项目id
#define MAX_SIZE 4096 // 创建的共享内存大小

key_t getKey() // 封装获取key的函数
{
    key_t k = ftok(PATHNAME, PROJ_ID);
    if (k == -1)
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

int getShmHelper(key_t key, int flags) // 封装通过key来获取shmid的函数
{
    int shmid = shmget(key, MAX_SIZE, flags);
    if(shmid < 0)
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
        exit(2);
    }
    return shmid;
}

int getShm(key_t key) // 用于找到已经创建的共享内存的shmid,所以传入的选项只有IPC_CREAT,不关心以前是否创建
{
    return getShmHelper(key, IPC_CREAT);
}

int createShm(key_t key) // 用于创建,所以传入的选项中有IPC_EXCL,表示如果遇到冲突就创建失败
{
    return getShmHelper(key, IPC_CREAT | IPC_EXCL | 0666); // 0666表示创建的共享内存的权限
}

void delShm(int shmid)
{
    if(shmctl(shmid, IPC_RMID, nullptr) == -1)// 这里不需要获取信息,传入nullptr即可
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
    }
}

void *attachShm(int shmid)
{
    void *mem = shmat(shmid, nullptr, 0);
    if((long long)mem == -1L) // 这里由于我们的机器是64位的,所以一个地址占8个字节,所以需要转成long long类型判断是否正确关联
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
        exit(3);
    }
    return mem;
}

void detachShm(void* start)
{
    if(shmdt(start) == -1)
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
    }
}
/*server.cc*/
#include "comm.hpp"

int main()
{
    key_t k = getKey();                     // 通过共同的pathname和proj_id构建一个相互通信的进程之间的key
    int shmid = createShm(k);               // 通过创建的key创建一段共享内存
    char *start = (char *)attachShm(shmid); // 将这段共享内存和当前进程地址空间关联

    // 使用共享内存通信
    while (true)
    {
        std::cout << "client say# " << start << std::endl; // 这里可以直接读取通信信息,因为地址相同
        struct shmid_ds ds;
        shmctl(shmid, IPC_STAT, &ds); //    获取shmid的相关信息
        printf("获取属性:size:%d,pid:%d,myself:%d", ds.shm_segsz, ds.shm_cpid);
        sleep(1);
    }

    delShm(shmid); // 使用完之后去关联
    delShm(shmid); // 谁创建的共享内存谁来释放
    return 0;
}
/*client*/
#include "comm.hpp"

int main()
{
    key_t k = getKey();                     // 通过共同的pathname和proj_id构建一个相互通信的进程之间的key
    int shmid = getShm(k);                  // 通过创建的key获取指定的共享内存
    char *start = (char *)attachShm(shmid); // 将这段共享内存和当前进程地址空间关联

    // 使用共享内存通信
    const char *message = "hello server,我是另一个进程,正在和你通信"; // 通信信息
    pid_t id = getpid();
    int count = 1;
    while (true)
    {
        sleep(5);
        snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, count++); // 直接讲通信信息写到start中即可
    }

    delShm(shmid); // 使用完之后去关联
    return 0;
}

image-20240120191913957

1.3.7 共享内存的特点

共享内存的生命周期是随OS的,而不是随进程的,这是所有System V进程间通信的共性

共享内存的优点:共享内存是所有进程间通信速度是最快的,因为共享内存是被双方所共享,只要写入对方就能立即看到,能大大减少数据的拷贝次数。

但是综合考虑管道和共享内存,考虑键盘输入,和显示器输出,对于同一份数据:共享内存有几次数据拷贝,管道有几次数据拷贝

管道:需要通过键盘输入到自己定义的缓冲区char buffer[],将数据拷贝到buffer中,调用write接口在把buffer里的数据拷贝到管道里,

另一进程也有定义buffer缓冲区,调用read读取把数据从管道里读取到buffer里,在把数据显示到显示器上:

img

共享内存:通过映射关系

img

共享内存的缺点:不给我们进行同步和互斥的操作,没有对数据做任何保护。客户端和服务端没做保护,如果想做保护要用到信号量,对共享内存进行保护,写完通过读端进行读取。

2. 消息队列(不常用)

2.1 消息队列的概念

消息队列是OS提供的内核级队列,消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法,每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

 struct msqid_ds {
     struct ipc_perm msg_perm;     /* Ownership and permissions */
     time_t          msg_stime;    /* Time of last msgsnd(2) */
     time_t          msg_rtime;    /* Time of last msgrcv(2) */
     time_t          msg_ctime;    /* Time of last change */
     unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
     msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
     msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
     pid_t           msg_lspid;    /* PID of last msgsnd(2) */
     pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
 };

消息队列数据结构的第一个成员是msg_perm,它和shm_perm是同一个类型的结构体变量,ipc_perm结构体的定义如下:

struct ipc_perm {
    key_t          __key;       /* Key supplied to msgget(2) */
    uid_t          uid;         /* Effective UID of owner */
    gid_t          gid;         /* Effective GID of owner */
    uid_t          cuid;        /* Effective UID of creator */
    gid_t          cgid;        /* Effective GID of creator */
    unsigned short mode;        /* Permissions */
    unsigned short __seq;       /* Sequence number */
};

2.2 消息队列的使用

2.2.1 获取消息队列

msgget

image-20240120192645998

2.2.2 控制消息队列

msgctl

image-20240120192711808

2.2.3 发送和接收数据

msgsndmsgrcv

image-20240120192752479

3. 信号量

关于信号量的知识,我们将会在后面多线程的地方详细讲解,这里先进行一些概念的扫盲

3.1 信号量的概念扫盲

  • 信号量的本质是一个计数器**,通常用来表示公共资源中,资源数的多少问题。信号量主要用于同步和互斥的。

  • 公共资源:能被多个进程同时访问的资源,访问没有保护的公共资源可能会导致数据不一致问题。要让不同的进程看到同一份资源是为了通信,通信是为了让进程间实现协同,而进程之间具有独立性,所以为了解决独立性问题要让进程看到同一份资源,但是会导致数据不一致的问题。

  • 临界资源:被保护起来的公共资源

  • 临界区:进程要使用资源一定是该进程有对应的代码来访问这部分临界资源,这段代码就是临界区,但是多个进程看到同一份资源是少数情况,大部分申请自己的资源用自己的代码区访问。

  • 非临界区:不访问公共资源的代码。

如何保护公共资源:互斥&&同步

互斥:由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥

原子性:要么不做、要么做完两态的这种情况。比如支付转账

如果用全局的整数来替代信号量?

全局的整数在父子关系的进程上都看不到,要发生写时拷贝,而不同的进程更看不到,所以进程间想看到同一个计数器得让进程看到同一个计数器。

为什么要信号量?

当我们想要某种资源的时候可以通过信号量进行预;,共享资源被使用的方式:作为一个整体使用;划分成为一个一个的资源部分

3.2 内核中信号量的相关数据结构

struct semid_ds {
    struct ipc_perm sem_perm;  /* Ownership and permissions */
    time_t          sem_otime; /* Last semop time */
    time_t          sem_ctime; /* Last change time */
    unsigned long   sem_nsems; /* No. of semaphores in set */
};

信号量数据结构的第一个成员也是ipc_perm类型的结构体变量,ipc_perm结构体的定义如下:

struct ipc_perm {
    key_t          __key; /* Key supplied to semget(2) */
    uid_t          uid;   /* Effective UID of owner */
    gid_t          gid;   /* Effective GID of owner */
    uid_t          cuid;  /* Effective UID of creator */
    gid_t          cgid;  /* Effective GID of creator */
    unsigned short mode;  /* Permissions */
    unsigned short __seq; /* Sequence number */
};

3.3 信号量的PV操作

我们知道信号量本质上就是一个临界资源的计数器,有进程要使用这个临界资源,就会导致可用的临界资源数量减少,使用完之后归还临界资源会导致可用的临界资源增多

  • P操作:向OS预定临界资源,会导致现有临界资源减少
  • V操作:向OS归还临界资源,会导致现有临界资源增加

假设信号量为sem,那么P操作相当于sem++,V操作相当于sem--,注意这里的++ 和 - -操作都是原子的

如果信号量的初始值是1就代表了访问公共资源作为一个整体来使用。二元信号量提供互斥功能

3.4 信号量相关函数

3.4.1 申请信号量

semget

image-20240120194422012

3.4.2 控制信号量

semctl

image-20240120194438064

3.4.3 信号量的操作PV

semop

image-20240120194454493


关于system V标准的进程间通信的思考

我们可以发现,共享内存、消息队列、信号量接口相似度非常高,获取与删除,都是system V标准的进程间通信。

OS如何管理:先描述,在组织,对相关资源的内核数据结构做管理,对于共享内存、消息队列、信号量的第一个成员都是ipc_perm:

struct ipc_perm {
    key_t          __key;    /* Key supplied to shmget(2) */
    uid_t          uid;      /* Effective UID of owner */
    gid_t          gid;      /* Effective GID of owner */
    uid_t          cuid;     /* Effective UID of creator */
    gid_t          cgid;     /* Effective GID of creator */
    unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
    unsigned short __seq;    /* Sequence number */
};

虽然内部的属性差别很大,但是维护它们的数据结构的第一个成员确实一样的,都是ipc_perm类型的成员变量,都可以通过key来标识唯一性。这样设计的好处:在操作系统内可以定义一个struct ipc_perm类型的数组,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。((struct shmid_ds*)perms[0],强转,此时就可以访问其他剩下的属性)


本节完…

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux/Traceback

Enumeration nmap 使用nmap初步扫描发现只开放了22和80端口&#xff0c;端口详细扫描情况如下 先看看web是什么样子的&#xff0c;打开网站发现有一条留言&#xff0c;显示该站点已经被黑了&#xff0c; 并且留下了后门 查看源代码&#xff0c;可以看到下面的注释 <!--So…

(C语言)编译和链接

前言͟͟͞͞&#x1f48c;&#xff1a;对于现在的各种编译器而言许多都是好多个功能的集成&#xff0c;那么我们的代码到底是如何去实现的呢&#xff1f;难道我们的计算机可以直接读懂我们所写的代码&#xff0c;并运行吗&#xff1f;对于很多细心的小伙伴们可能会想这样的问题…

Arduino开发实例-INA219 电流传感器驱动

INA219 电流传感器驱动 文章目录 INA219 电流传感器驱动1、INA219 电流传感器介绍2、硬件准备及接线3、代码实现1、INA219 电流传感器介绍 INA219 模块用于同时测量电流和电压。 该模块使用 I2C 通信传输电压和电流数据。 其他特性: 测量精度:1%最大测量电压:26V最大测量电…

解决小程序字体在最左上角问题

问题如下 原因&#xff1a; 出现这种现象的原因是项目默认开启了Skyline渲染模式&#xff0c;因为Skyline渲染模式不支持原生导航栏&#xff0c;所以在json文件中设置的导航栏失效&#xff0c;文字就会向上移动&#xff0c;如果想要使用原生的导航栏&#xff0c;可以将app.jso…

C#,入门教程(20)——列表(List)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(19)——循环语句&#xff08;for&#xff0c;while&#xff0c;foreach&#xff09;的基础知识https://blog.csdn.net/beijinghorn/article/details/124060844 List顾名思义就是数据列表&#xff0c;区别于数据数组&#xff08;arr…

centos7安装Redis7.2.4

文章目录 下载Redis解压Redis安装gcc依赖&#xff08;Redis是C语言编写的&#xff0c;编译需要&#xff09;编译安装src目录下二进制文件安装到/usr/local/bin修改redis.conf文件启动redis服务外部连接测试 参考&#xff1a; 在centos中安装redis-5.0.7 Memory overcommit must…

基于JavaWeb+SSM+Vue停车场微信小程序系统的设计和实现

基于JavaWebSSMVue停车场微信小程序系统的设计和实现 滑到文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 滑到文末获取源码 Lun文目录 目录 1系统概述 1 1.1 研究背景 1 1.2研究目的 1 1.3系统设计思想 1 2相关…

Linux命令手册

简介 Multics&#xff08;大而全&#xff09;项目失败&#xff0c;吸取教训启动Unix&#xff08;小而精&#xff09;&#xff0c;Linus Benedict Torvalds受Unix启发开发初始版本Linux内核&#xff0c;Git也由其开发&#xff0c;目的是为了更好的管理Linux内核开发。Unix是商业…

有线桥接|Wifi隔了一堵墙就没信号?房间的网线口利用起来,让房间死角也有网!

前言 本篇文章是路由器有线桥接主路由&#xff0c;起到AP&#xff08;热点&#xff09;的效果 上次发布的无线桥接&#xff0c;使用的前提是需要把旧路由放置在主路由的信号范围内&#xff0c;这极大限制了桥接路由器的放置位置。 如果隔了一堵墙基本上就无法连接Wifi&#x…

供应链安全项目in-toto开源框架详解

引言&#xff1a;in-toto 是一个开源框架&#xff0c;能够以密码学的方式验证构件生产路径上的每个组件和步骤。它可与主流的构建工具、部署工具进行集成。in-toto已经被CNCF技术监督委员会 (Technical Oversight Committee&#xff0c;TOC)接纳为CNCF孵化项目。 1. 背景 由于…

微信小程序之WXML 模板语法之数据绑定、事件绑定、wx:if和列表渲染

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

一键搭建你的知识库

效果 说明 由于安装包安装需要glibc>2.7 我就不尝试了 因为glib升级是一个繁琐的过程 没有升级的意义 只是为了体验知识库 没必要浪费时间 1.1docker compose部署trilium 1.1.创建目录 mkdir -p /opt/triliumcd /opt/trilium 1.2.编写docker-comppose.yml文件 vim dock…

使用STM32的GPIO口实现LED闪烁

✅作者简介&#xff1a;热爱科研的嵌入式开发者&#xff0c;修心和技术同步精进 代码获取、问题探讨及文章转载可私信。 ☁ 愿你的生命中有够多的云翳,来造就一个美丽的黄昏。 &#x1f34e;获取更多嵌入式资料可点击链接进群领取&#xff0c;谢谢支持&#xff01;&#x1f447…

JavaWeb-Cookie与Session

一、概念 是否还记得我们在HTTP概念中提到&#xff1a;HTTP的一大特点是无状态&#xff0c;这意味着多次HTTP请求之间是无法共享数据的。而在请求之间共享一些数据又是我们期望达到的效果。&#xff08;例如登录的记住我功能&#xff09;于是便有了会话跟踪技术&#xff0c;而…

Qt拖拽组件与键盘事件

1.相关说明 1.设置widget或view的拖拽和放置模式函数setDragDropMode参数说明&#xff0c;NoDragDrop(无拖拽和放置)、DragOnly(只允许拖拽)、DropOnly(只允许放置)、DragDrop(允许拖拽和放置)、InternalMove(只移动不复制) 2.设置widget或view的放置动作函数setDefaultDropAct…

python 实现大语言模型中的概率论:两人轮流出手对决时取胜概率的推导

假设你跟朋友通过打赌投篮来打赌一万块。你们找到一个篮球框&#xff0c;然后约定轮流投篮&#xff0c;谁先投进谁赢。假设你投进的概率是 p&#xff0c;也就是投不进的概率是 1-p&#xff0c;你对手投进的概率是 q,投不进的概率是 1-q&#xff0c;如果由你先投&#xff0c;那么…

反序列化提升刷题(2)

今天的例题&#xff1a; <?phphighlight_file(__FILE__);class ctfshowvip{public $username;public $password;public $code;public function __construct($u,$p){$this->username$u;$this->password$p;}public function __wakeup(){if($this->username! || $thi…

解决 vue 项目开发越久 node_modules包越大的问题

解决 vue 项目开发越久 node_modules包越大的问题 node_modules.cache 文件&#xff08;编译缓存文件 可以删除 &#xff09; compression-webpack-plugin 禁止缓存 const CompressionPlugin require("compression-webpack-plugin");module.exports {plugins: [ne…

面向对象之深度优先和广度优先

面向对象深度优先和广度优先是什么&#xff1f; 二叉树的两种遍历是数据结构的经典考察题目, 广度遍历考察队列结构, 深度遍历考察递归 深度优先 先序遍历(父, 左子, 右子) 0, 1, 3, 7, 8, 4, 9, 2, 5, 6 中序遍历(左子, 父, 右子) 7, 3, 8, 1, 9, 4, 0, 5, 2, 6 后序遍历(左子…

SpringBoot跨域问题解决

前端访问后台接口时&#xff0c;浏览器报错&#xff0c;跨域无法访问。 报错信息如下&#xff1a; Response to preflight request doesnt pass access control check: No Access-Control-Allow-Origin header is present on the requested resource. 经过一番百度之后&#…