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

news2024/10/7 16:21:09

需要云服务器等云产品来学习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],强转,此时就可以访问其他剩下的属性)


本节完…

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

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

相关文章

在Java中调企微机器人发送消息到群里

目录 如何使用群机器人 消息类型及数据格式 文本类型 markdown类型 图片类型 图文类型 文件类型 模版卡片类型 文本通知模版卡片 图文展示模版卡片 消息发送频率限制 文件上传接口 Java 执行语句 String url "webhook的Url"; String result HttpReque…

二叉树的直径(LeetCode 543)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路参考文献 1.问题描述 给你一棵二叉树的根节点&#xff0c;返回该树的直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的长度由它们之间边数…

Odrive 学习系列四:如何使用脚本自动初始化odrive配置

一、背景: 在学习markbase的教程后,发现odrive的初始化配置命令确实有点多。尽管odrive有自动补全: 且可以通过 ctrl + → 来快速补全: 但是对初学者而言,仍旧有比较大的工作量。 而针对于此,我们可以通过powershell脚本的方式来解决这个问题。 二、设计初始化…

继电器开关电路图大全

继电器是一种电控制器件&#xff0c;是当输入量&#xff08;激励量&#xff09;的变化达到规定要求时&#xff0c;在电气输出电路中使被控量发生预定的阶跃变化的一种电器。它具有控制系统&#xff08;又称输入回路&#xff09;和被控制系统&#xff08;又称输出回路&#xff0…

Siemens-NXUG二次开发-导入与导出(可移除参数)prt文件[Python UF][20240121]

Siemens-NXUG二次开发-导入与导出&#xff08;可移除参数&#xff09;prt文件[Python UF][20240121] 1.python uf函数1.1 NXOpen.UF.Part.Import1.2 NXOpen.UF.Part.ImportPartModes1.3 NXOpen.UF.Group.AskGroupData1.4 NXOpen.UF.Obj.AskTypeAndSubtype1.5 NXOpen.UF.Part.Ex…

Frenet坐标系下动态街道场景的最优轨迹生成

0 前言 有两个主要算法已经在实际中使用&#xff1a; &#xff08;1&#xff09;大多数研究组采用插值来解决规划问题&#xff0c;如奥迪、斯坦福最近演示中使用了回旋曲线&#xff0c;贝塞尔以及多项式曲线也被其他研究组使用。主要原因是在结构化环境中增强映射可以提供所需…

第十一站:多态练习ODU

实现动态切换 ODU.h #pragma once #include <iostream> using namespace std; #define ODU_TYPE_311_FLAG "311" #define ODU_TYPE_335_FLAG "335" enum class ODU_TYPE {ODU_TYPE_311,ODU_TYPE_335,ODU_TYPE_UNKNOW };class ODU{ public:ODU();//发…

Jan AI本地运行揭秘:首次体验,尝鲜科技前沿

&#x1f31f;&#x1f30c; 欢迎来到知识与创意的殿堂 — 远见阁小民的世界&#xff01;&#x1f680; &#x1f31f;&#x1f9ed; 在这里&#xff0c;我们一起探索技术的奥秘&#xff0c;一起在知识的海洋中遨游。 &#x1f31f;&#x1f9ed; 在这里&#xff0c;每个错误都…

带POE网络变压器与2.5G/5G/10G网络变压器产品特点介绍

Hqst华轩盛(石门盈盛)电子导读&#xff1a;一起来了解带POE网络变压器与2.5G/5G/10G网络变压器产品特点&#xff1f; 一﹑带POE网络变压器与2.5G/5G/10G网络变压器产品特点介绍 首先、POE网络变压器产品与常规不带POE产品的区别&#xff1a; 带POE网络变压器主要要求是耐电流等…

按键检测知识

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;…

frida https抓包

web端导入证书、https代理即可解决大部分需求&#xff0c;但是&#xff0c;有些app需要处理ssl pinning验证。 废话不多说。frida处理ssl pin的步骤大体如下。 安装python3.x,并在python环境中安装frida&#xff1a; pip install frida pip install frida-tools下载frida-se…

C#,入门教程(22)——函数的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(21)——命名空间&#xff08;namespace&#xff09;与程序结构的基础知识https://blog.csdn.net/beijinghorn/article/details/124140653 一、函数的基本概念 一个软件的结构大体如下&#xff1a; 大厦application: a plaza { --…

分布式锁实现(mysql,以及redis)以及分布式的概念

道生一&#xff0c;一生二&#xff0c;二生三&#xff0c;三生万物 我旁边的一位老哥跟我说&#xff0c;你知道分布式是是用来干什么的嘛&#xff1f;一句话给我干懵了&#xff0c;我能隐含知道&#xff0c;大概是用来做分压处理的&#xff0c;并增加系统稳定性的。但是具体如…

最优传输学习及问题总结

文章目录 参考内容lam0.1lam3lam10lam50lam100lam300画图线性规划matlabpython代码 参考内容 https://blog.csdn.net/qq_41129489/article/details/128830589 https://zhuanlan.zhihu.com/p/542379144 我主要想强调的是这个例子的解法存在的一些细节问题 lam0.1 lam 0.1P,…

《WebKit 技术内幕》之五(3): HTML解释器和DOM 模型

3 DOM的事件机制 基于 WebKit 的浏览器事件处理过程&#xff1a;首先检测事件发生处的元素有无监听者&#xff0c;如果网页的相关节点注册了事件的监听者则浏览器会将事件派发给 WebKit 内核来处理。另外浏览器可能也需要处理这样的事件&#xff08;浏览器对于有些事件必须响应…

D - Left Right Operation

思路&#xff1a; 1、求前缀和 2、从后往前遍历&#xff0c;把某个后缀都变为R&#xff0c;记录最多让数组和减小多少 3、从前往后遍历&#xff0c;把某个前缀都变为L&#xff0c;记录最小答案&#xff08;前i个变为L&#xff0c;后面的n-i个数让减小最多的后缀变为R&#x…

项目管理十大知识领域之项目采购管理

一、项目采购管理的定义与概述 项目采购管理是指在项目实施过程中&#xff0c;对相关产品、服务或工程进行采购的管理活动。其概述包括确定采购需求、制定采购计划、供应商选择、合同签订、供应管理和结算支付等环节。项目采购管理的定义还涉及对采购目标的明确界定&#xff0…

[小程序]使用代码渲染页面

一、条件渲染 1.单个控制 使用wx:if"{{条件}}"来判断是否需要渲染这段代码&#xff0c;同时可以结合wx:elif和wx:else来判断 <view wx:if"{{type0}}">0</view> <view wx:elif"{{type1}}">1</view> <view wx:else>…

数字IC后端设计实现 | PR工具中到底应该如何控制density和congestion?(ICC2Innovus)

吾爱IC社区星友提问&#xff1a;请教星主和各位大佬&#xff0c;对于一个模块如果不加干预工具会让inst挤成一团&#xff0c;后面eco修时序就没有空间了。如果全都加instPadding会导致面积不够overlap&#xff0c;大家一般怎么处理这种问题&#xff1f; 在数字IC后端设计实现中…

C#中ArrayList运行机制及其涉及的装箱拆箱

C#中ArrayList运行机制及其涉及的装箱拆箱 1.1 基本用法1.1.1 属性1.1.2 方法 1.2 内部实现1.3 装箱1.4 拆箱1.5 object对象的相等性比较1.6 总结1.7 其他简单结构类 1.1 基本用法 命名空间&#xff1a; using System.Collections; 1.1.1 属性 Capacity&#xff1a;获取或设…