System V 共享内存

news2024/11/15 8:06:44

System V 共享内存

  • 共享内存是什么
  • 如何使用共享内存
    • ftok
    • shmget
    • shmat
    • shmdt
    • shmctl
  • 共享内存的原理
  • 共享内存实现两个进程间通信
  • 共享内存的特点
  • 共享内存与管道配合使用
    • 两个进程间通信
    • 多个进程间通信

共享内存是什么

🚀共享内存是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间的数据传递不在涉及到内核,换句话说是进程不在通过执行进入内核的系统调用来传递彼此的数据。
🚀共享内存是物理内存的一块区域,通过页表的映射挂接到共享它的进程的地址空间上,再返回给用户这段空间的起始地址,这样用户就能使用这一块内存了。
🚀共享内存与管道不同,管道创建好后,仍需使用系统调用接口来从管道中读取或写入数据,并且管道只允许一端读一端写,而共享内存是直接使用这块内存空间。

如何使用共享内存

ftok

在这里插入图片描述

  • 参数: 第一个参数是文件名,并且是已经存在的文件,第二个参数是项目的id(可以随便填写)
  • 返回值: 返回一个整型的key值,这个key值起到唯一标识的作用。如果失败返回-1。
  • 用途: 这个函数是通过所传入的一个字符串和一个整形数,根据它内部的一套算法尽可能的产生一个唯一标示的key值,而这个key值会在创建共享内存的时候使用。

shmget

在这里插入图片描述

  • 参数: 第一个参数就是通过ftok产生的key值,第二个参数是所要创建的共享内存的大小,第三个参数是shmflag,通常使用IPC_CREAT和IPC_EXCL。

    IPC_CREAT : 创建一个共享内存,如果已经存在那么就是用已经存在的共享内存。
    IPC_EXCL : 这个选项无法单独使用通常是配合IPC_CREAT使用,其作用是创建一个共享内存,如果此共享内存已经存在就报错返回,其目的就是创建一个全新的共享内存。

  • 返回值: 如果创建成功,那么返回共享内存的表示符shmid,如果创建失败就返回-1。

🚀key值的作用:

1.确保进行进程间通信的进程可以看到同一块共享内存。
2.确保创建出来的共享内存与其他已经存在的共享内存冲突。

共享内存使几个进程间得到通信,但是系统中存在大量的进程,可能会创建出大量的共享内存,OS势必会对这些共享内存进行管理操作,也就是说内核中肯定会存在描述共享内存的结构体,里面存储了关于共享内存的许多字段。而这些一个个的结构体对象肯定也会通过某种数据结构组织起来。而key值就会被存储在结构体中,在创建共享内存的时候,会先遍历已存在的共享内存的结构体对象,查看当前的key值是否已经在之前出现过,如果使用的是IPC_CREAT选项,若key值已经出现过那么就返回这块共享内存的标识符(类似于数组下标的东西),如果加上了IPC_EXCL选项,key值已经存在的话就会报错返回。

在这里插入图片描述

实际上不一定是以链表这种数据结构来存储的。key值实际上是在shmid_ds结构体对象的第一个成员shm_perm(也是个结构体)中存储的。

在这里插入图片描述

shmat

在这里插入图片描述

  • 参数: 第一个参数是共享内存的表示符,第二个参数是选择挂接的地址,通常我们设置为nullptr,意思是交给OS自动处理,第三个参数通常默认为0。
  • 返回值: 返回void的指针,与malloc的返回值十分类似,通常我们将其强转为char 类型的指针使用。如果挂接失败则返回(void*)-1。

前面提到,用户创建完共享内存后,要将共享内存挂接到共享它的进程的地址空间上,就是通过shmat函数来完成的,与动态库类似,动态库首先被加载到物理内存,通过页表的映射作用,映射到进程地址空间的共享区。

shmdt

在这里插入图片描述

  • 参数: 共享内存的首地址。
  • 返回值: 去关联成功的话返回0,否则返回-1。

进程在使用共享内存前首先要与共享内存关联,使用完毕后要与共享内存去关联。

shmctl

在这里插入图片描述

  • 参数: 第一个参数是共享内存标识符,第二个参数是命令选项如果是删除共享内存的话就选择IPC_RMID,相应的第三个参数设置为nullptr,如果第二个参数选择的是IPC_SET是将内核数据结构的信息提取出来,那么第三个参数就为缓冲区的地址。
  • 返回值: 成功的话返回0,否则返回-1。

🚀试着获取内核数据结构:

#define SERVER 0
#define CLIENT 1
class Init
{
public:
    Init(int n)
        : type(n)
    {
        int key = get_key();
        if (type == SERVER)
            shmid = create_shm(key, gsize);
        else
            shmid = get_shm(key, gsize);
        start = attch_shm(shmid);
    }
    char *getstart()
    {
        return start;
    }
    int getshmid()
    {
        return shmid;
    }
    ~Init()
    {
        detach_shm(start);
        if (type == SERVER)
            del_shm(shmid);
    }

private:
    char *start;
    int type;
    int shmid;
};
int main()
{
    Init init(SERVER);
    struct shmid_ds p;
    shmctl(init.getshmid(), IPC_SET, &p);
    cout << "key : " << p.shm_perm.__key << endl;
    return 0;
}

在这里插入图片描述

共享内存的原理

在这里插入图片描述
🚀共享内存是物理内存的一段,通过页表映射到共享它的进程的地址空间上,并且将这块空间的首地址返回给用户。这样两个进程就可以看到同一份资源,可以进行进程间通信了。
🚀系统同存在许多共享内存,OS为了管理这些共享内存,会为其创建结构体对象例如struct shmid_ds,里面存储了关于共享内存的属性。key值被存储在struct shmid_ds内部的第一个成员struct shm_perm中。
🚀对于多个这样像shmid_ds这样的结构体,在内核中会议某种数据结构将它们组织起来。所谓的shmid共享内存标识符就类似于数组的下标。
🚀key值是在内核中使用的,shmid是在用户层使用的。

System V标准的进程间通信的方式,除了共享内存外还有消息队列和信号量。
描述这三个东西的结构体中都有 struct ipc_perm这个结构体,在内核中其实是将这个结构体的指针组织了起来。无论是共享内存,还是消息队列,还是信号量,它们都有这个结构体,它们三个组织在同一数据结构中的。由于perm这个结构体是第一个成员,所以perm的地址与整个结构体的地址是相同的,所以如果想得到整个结构体,可以通过指针强转的方式得到。

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

共享内存实现两个进程间通信

由于两个进程使用共享内存进行通信时,所用的系统调用接口大致相似,所以我们把这些接口进行简单的封装放在头文件中。即使这样两个进程在调用这些接口的代码也十分相似,所以可以封装成一个类,这个类的构造函数中进行共享内存的创建和挂接,析构函数中进行去关联和删除共享内存,这样就简化了代码,并且使代码更美观。

🚀使用共享内存的一般步骤:

  • 创建共享内存
  • 共享内存与进程关联
  • 共享内存与进程去关联
  • 释放共享内存空间

🚀comm.hpp

#pragma once
#include <iostream>
using namespace std;
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <cstdio>
#include <sys/shm.h>

#define PATHNAME "."
#define PROJID 1234

const int gsize = 4096;
key_t get_key()
{
    key_t n = ftok(PATHNAME, PROJID);
    if (n == -1)
    {
        perror("ftok");
        exit(1);
    }
    return n;
}

int create_shm_helper(int key, size_t size, int shmflag)
{
    int shm_id = shmget(key, size, shmflag);
    if (shm_id == -1)
    {
        perror("shmget");
        exit(2);
    }
    return shm_id;
}

int create_shm(int key, size_t size)
{
    return create_shm_helper(key, size, IPC_CREAT | IPC_EXCL | 0664);
}

int get_shm(int key, size_t size)
{
    return create_shm_helper(key, size, IPC_CREAT);
}

char *attch_shm(int shm_id)
{
    char *start = (char *)shmat(shm_id, nullptr, 0);
    if ((void *)start == (void *)-1)
    {
        perror("shmat");
        exit(3);
    }
    return start;
}
void detach_shm(char *start)
{
    int n = shmdt(start);
    if (n == -1)
    {
        perror("shmdt");
        exit(4);
    }
}
void del_shm(int shmid)
{
    int n = shmctl(shmid, IPC_RMID, nullptr);
    if (n == -1)
    {
        perror("shmctr");
        exit(5);
    }
}
#define SERVER 0
#define CLIENT 1
class Init
{
public:
    Init(int n)
        : type(n)
    {
        int key = get_key();
        if (type == SERVER)
            shmid = create_shm(key, gsize);
        else
            shmid = get_shm(key, gsize);
        start = attch_shm(shmid);
    }
    char *getstart()
    {
        return start;
    }
    int getshmid()
    {
        return shmid;
    }
    ~Init()
    {
        detach_shm(start);
        if (type == SERVER)
            del_shm(shmid);
    }

private:
    char *start;
    int type;
    int shmid;
};

🚀server.cpp

#include "comm.hpp"

int main()
{
    Init init(SERVER);
    // struct shmid_ds p;
    // shmctl(init.getshmid(), IPC_SET, &p);
    // cout << "key : " << p.shm_perm.__key << endl;
    char *start = init.getstart();
    int n = 0;
    while (n < 30)
    {
        cout << start << endl;
        sleep(1);
        n++;
    }
    return 0;
}

🚀client.cpp

#include "comm.hpp"
int main()
{
    Init init(CLIENT);
    char *start = init.getstart();
    char ch = 'A';
    while (ch <= 'Z')
    {
        start[ch - 'A'] = ch;
        ch++;
        start[ch - 'A'] = '\0';
        sleep(1);
    }
    return 0;
}

这份小的代码就是让两个进程实现通信,客户端向共享内存中写入,服务端从共享内存中读取并打印到显示器。

🚀运行效果
在这里插入图片描述

共享内存的特点

🚀共享内存是进程间通信最快的方式,创建好共享内存并与进程关联后,就可以直接使用这块空间,与管道相比,管道实现通信的方式通过read,write接口来访问管道资源的,这样就会比共享内存多了两次数据在缓冲区之间拷贝的操作,这也就是为什么管道的速度比共享内存慢了。
🚀共享内存没有同步与互斥的机制做保护,管道通信的话,写端在向管道中写数据的时候,读端会阻塞直到写端把数据写完读端才能读取数据,而共享内存不存在这种保护机制,数据的写入和读取可以同时进行,这回造成许多问题。
🚀共享内存的声明周期是随操作系统的,而管道的声明周期是随进程的。
🚀共享内存的大小是向上对其到页大小的整数倍的。
在这里插入图片描述
操作系统中空间的大小是以页为单位的,每页的大小是4096字节。通过ipcs -m 指令可以查看共享内存的属性。
在这里插入图片描述
可以看到这里显示共享内存的大小是4097字节,但其实OS为其开辟了8KB的空间,只是允许用户使用4097大小的空间。

🚀使用ipcrm -m shmid 删除共享内存
在这里插入图片描述

🚀共享内存也是有权限的,在使用shmget系统调用创建共享内存的时候,其三个参数shmflag可以加上共享内存的权限,IPC_CREAT | IPC_EXCL | 0666,这样传参。
在这里插入图片描述
🚀nattch表示有几个进程与共享内存进行关联。

共享内存与管道配合使用

两个进程间通信

🚀上面提到共享内存没有同步与互斥的保护机制,通常是与其他方式配合使用的。我们可以通过共享内存与命名管道相配合的方式来完成进程间通信。在客户端与服务端之间创建两个命名管道,这两个管道的作用是,写端向共享内存写完数据后向读端发送一个信号使其开始在共享内存中读取数据,通向读取完成后给它再给写端发送信号,告诉写端现在可以向共享内存中写入数据了,这样可以达到写端写完数据读端才能读取,读端读取完毕后写端才能继续写入数据。

comm.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cerrno>
#include <fcntl.h>
#include <sys/ipc.h>
#include <cstdlib>
#include <sys/shm.h>
#include <cstring>
using namespace std;

#define S_TO_C "pipe1"
#define C_TO_S "pipe2"
const int gsize = 4096;
#define PATHNAME "."
#define PROJ_ID 1234

key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJ_ID);
    if (k == -1)
    {
        perror("ftok");
        exit(5);
    }
    return k;
}

int createShmHelper(key_t key, int flag)
{
    int shmid = shmget(key, gsize, flag);
    if (shmid == -1)
    {
        perror("shmget");
        exit(6);
    }
    return shmid;
}

int createShm(key_t key)
{
    return createShmHelper(key, IPC_CREAT | IPC_EXCL | 0664);
}

int getShm(key_t key)
{
    return createShmHelper(key, IPC_CREAT);
}

char *attchShm(int shmid)
{
    char *start = (char *)shmat(shmid, nullptr, 0);
    if ((void *)start == (void *)-1)
    {
        perror("shmat");
        exit(7);
    }
    return start;
}

void detachShm(char *start)
{
    int n = shmdt(start);
    if (n == -1)
    {
        perror("shmdt");
        exit(9);
    }
}

void delShm(int shmid)
{
    int n = shmctl(shmid, IPC_RMID, nullptr);
    if (n == -1)
    {
        perror("shmctl");
        exit(8);
    }
}

#define SERVER 0
#define CLIENT 1
class Init
{
public:
    Init(int n)
        : type(n)
    {
        key_t key = getKey();
        if (type == SERVER)
            shmid = createShm(key);
        else
            shmid = getShm(key);
        start = attchShm(shmid);
    }
    char *getstart()
    {
        return start;
    }
    ~Init()
    {
        detachShm(start);
        if (type == SERVER)
            delShm(shmid);
    }

private:
    char *start;
    int type;
    int shmid;
};

server.cpp

#include "comm.hpp"
int main()
{
    // 1.创建两个命名管道
    int n = mkfifo(S_TO_C, 0664);
    if (n == -1)
    {
        cerr << errno << endl;
        return 1;
    }
    int m = mkfifo(C_TO_S, 0664);
    if (m == -1)
    {
        cerr << errno << endl;
        return 2;
    }
    // 2.服务端以读的方式打开pipe2,写的方式打开pipe1
    cout << "----------------" << endl;
    int wfd = open(S_TO_C, O_WRONLY);
    cout << "----------------" << endl;
    if (wfd == -1)
    {
        cerr << errno << endl;
        return 3;
    }
    int rfd = open(C_TO_S, O_RDONLY);
    if (rfd == -1)
    {
        cerr << errno << endl;
        return 4;
    }

    // 3.共享内存相关工作
    Init init(SERVER);
    char *start = init.getstart();
    // 通信
    int send = 1;
    write(wfd, &send, sizeof(send));
    while (true)
    {
        int receive = 0;
        // 客户端写完数据,发送信号,服务端接收到信号后再读取
        read(rfd, &receive, sizeof(receive));
        if (receive)
        {
            cout << "收到来自客户端允许读取数据的信号" << endl;
            cout << start << endl;
        }
        else
        {
            break;
        }
        // 读取完给客户端发送信号,让其继续写入数据
        write(wfd, &send, sizeof(send));
        sleep(1);
    }
    // 4.关闭管道
    close(wfd);
    close(rfd);
    unlink(S_TO_C);
    unlink(C_TO_S);
    return 0;
}

client.cpp

#include "comm.hpp"

int main()
{
    // 2.服务端以读的方式打开pipe1,写的方式打开pipe2
    int rfd = open(S_TO_C, O_RDONLY);
    if (rfd == -1)
    {
        cerr << errno << endl;
        return 4;
    }
    int wfd = open(C_TO_S, O_WRONLY);
    if (wfd == -1)
    {
        cerr << errno << endl;
        return 3;
    }
    sleep(1);
    // 3.共享内存相关工作
    Init init(CLIENT);
    char *start = init.getstart();
    // 通信
    int cnt = 1;
    while (true)
    {
        int receive = 0;
        read(rfd, &receive, sizeof(receive));
        if (receive)
        {
            cout << "收到服务端信号,继续写入" << endl;
            memset(start, '\0', gsize);
            snprintf(start, 4096, "%d->%s", cnt++, "hello shm_pipe");
            // strcpy(start, "hello shm_pipe\n");
        }
        int send = 1;
        write(wfd, &send, sizeof(send));
        sleep(1);
    }

    // 4.关闭管道
    close(wfd);
    close(rfd);
    return 0;
}

🚀程序的运行效果:
在这里插入图片描述

多个进程间通信

🚀一块共享内存可以被多个进程关联,那么它可以有多个读端和多个写端,这里实现一个只有一个写端有多个读端的例子,首先创建若干个管道,管道的写端都是ctrlProcess进程,读端分别是process1,process2 … 当ctrlProcess进程向共享内存写入数据完成后,可以选择让哪个进程来读取共享内存中的数据。

comm.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cerrno>
#include <fcntl.h>
#include <sys/ipc.h>
#include <cstdlib>
#include <sys/shm.h>
#include <cstring>
#include <string>
#include <vector>
#include <cstdio>
using namespace std;

// 管道名称
const int N = 100;
struct Pipe
{
    static string pipe_name[N];
    static int _size;
    Pipe()
    {
    }

public:
    void Add(const char *str)
    {
        pipe_name[_size] = str;
        _size++;
    }
};
int Pipe::_size = 3;
string Pipe::pipe_name[N] = {
    "pipe1",
    "pipe2",
    "pipe3"};
const int gsize = 4096;
#define PATHNAME "."
#define PROJ_ID 1234

key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJ_ID);
    if (k == -1)
    {
        perror("ftok");
        exit(5);
    }
    return k;
}

int createShmHelper(key_t key, int flag)
{
    int shmid = shmget(key, gsize, flag);
    if (shmid == -1)
    {
        perror("shmget");
        exit(6);
    }
    return shmid;
}

int createShm(key_t key)
{
    return createShmHelper(key, IPC_CREAT | IPC_EXCL | 0664);
}

int getShm(key_t key)
{
    return createShmHelper(key, IPC_CREAT);
}

char *attchShm(int shmid)
{
    char *start = (char *)shmat(shmid, nullptr, 0);
    if ((void *)start == (void *)-1)
    {
        perror("shmat");
        exit(7);
    }
    return start;
}

void detachShm(char *start)
{
    int n = shmdt(start);
    if (n == -1)
    {
        perror("shmdt");
        exit(9);
    }
}

void delShm(int shmid)
{
    int n = shmctl(shmid, IPC_RMID, nullptr);
    if (n == -1)
    {
        perror("shmctl");
        exit(8);
    }
}

#define SERVER 0
#define CLIENT 1
class Init
{
public:
    Init(int n)
        : type(n)
    {
        key_t key = getKey();
        if (type == SERVER)
            shmid = createShm(key);
        else
            shmid = getShm(key);
        start = attchShm(shmid);
    }
    char *getstart()
    {
        return start;
    }
    ~Init()
    {
        detachShm(start);
        if (type == SERVER)
            delShm(shmid);
    }

private:
    char *start;
    int type;
    int shmid;
};

ctrlProcess.cpp

#include "comm.hpp"

class Endpoint
{
public:
    Endpoint(const string &str, int wfd)
        : _pipe_name(str), _wfd(wfd)
    {
    }

public:
    string _pipe_name;
    int _wfd;
};

int select_process()
{
    int select = 0;
    cout << "#####################" << endl;
    cout << "######0.process1#####" << endl;
    cout << "######1.process2#####" << endl;
    cout << "######2.process3#####" << endl;
    cout << "####    3.exit   ####" << endl;
    cout << "Please select# ";
    cin >> select;
    getchar();
    return select;
}
int main()
{
    // 创建共享内存
    Init init(SERVER);
    // 创建命名管道文件
    for (int i = 0; i < Pipe::_size; i++)
    {
        int n = mkfifo(Pipe::pipe_name[i].c_str(), 0664);
        if (n == -1)
        {
            perror("mkfifo");
            exit(-1);
        }
    }
    // 以写的方式打开这些命名管道
    vector<Endpoint> end_points;
    for (int i = 0; i < Pipe::_size; i++)
    {
        int wfd = open(Pipe::pipe_name[i].c_str(), O_WRONLY);
        if (wfd == -1)
        {
            perror("open");
            exit(-2);
        }
        end_points.push_back(Endpoint(Pipe::pipe_name[i], wfd));
    }

    // 通信
    char *start = init.getstart();
    while (true)
    {
        int select = select_process();
        if (select == 3)
            break;
        if (select < 0 || select > 3)
            continue;
        memset(start, '\0', 4096);
        cout << "向共享内存写入信息# ";
        fgets(start, 4000, stdin);
        // cin >> start;
        //  通知相应进程
        int command = 1;
        write(end_points[select]._wfd, &command, sizeof(int));
        sleep(2);
        cout << endl;
    }

    // 关闭pipe
    for (int i = 0; i < Pipe::_size; i++)
    {
        close(end_points[i]._wfd);
    }

    // unlink pipe
    for (int i = 0; i < Pipe::_size; i++)
    {
        unlink(end_points[i]._pipe_name.c_str());
    }
    return 0;
}

process1.cpp

#include "comm.hpp"
int main()
{
    // 打开相应管道
    int rfd = open(Pipe::pipe_name[0].c_str(), O_RDONLY);
    if (rfd == -1)
    {
        perror("open");
        exit(-3);
    }
    // 挂接共享内存
    Init init(CLIENT);
    char *start = init.getstart();
    // 接受来自ctrl的命令
    while (true)
    {
        int command = 0;
        int n = read(rfd, &command, sizeof(int));
        if (n == 4)
        {
            if (command == 1)
            {
                cout << "process1收到从共享内存读取数据的命令" << endl;
                cout << start;
            }
        }
        else if (n == 0)
        {
            cout << "写端关闭" << endl;
            break;
        }
        else
            break;
    }
    // 关闭pipe
    close(rfd);
    return 0;
}

process2.cpp

#include "comm.hpp"

int main()
{
    // 打开相应管道
    int rfd = open(Pipe::pipe_name[1].c_str(), O_RDONLY);
    if (rfd == -1)
    {
        perror("open");
        exit(-3);
    }
    // 挂接共享内存
    Init init(CLIENT);

    char *start = init.getstart();
    // 接受来自ctrl的命令
    while (true)
    {
        int command = 0;
        int n = read(rfd, &command, sizeof(int));
        if (n == 4)
        {
            if (command == 1)
            {
                cout << "process2收到从共享内存读取数据的命令" << endl;
                cout << start;
            }
        }
        else if (n == 0)
        {
            cout << "写端关闭" << endl;
            break;
        }
        else
            break;
    }
    close(rfd);

    return 0;
}

process3.cpp

#include "comm.hpp"
int main()
{
    // 打开相应管道
    int rfd = open(Pipe::pipe_name[2].c_str(), O_RDONLY);
    if (rfd == -1)
    {
        perror("open");
        exit(-3);
    }
    // 挂接共享内存
    Init init(CLIENT);
    char *start = init.getstart();
    // 接受来自ctrl的命令
    while (true)
    {
        int command = 0;
        int n = read(rfd, &command, sizeof(int));
        if (n == 4)
        {
            if (command == 1)
            {
                cout << "process3收到从共享内存读取数据的命令" << endl;
                cout << start;
            }
        }
        else if (n == 0)
        {
            cout << "写端关闭" << endl;
            break;
        }
        else
            break;
    }
    close(rfd);

    return 0;
}

🚀运行效果:
在这里插入图片描述

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

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

相关文章

【产品应用】一体化步进伺服电机在高速异形插件机的应用

随着科技的不断发展&#xff0c;自动化生产设备在各个行业中得到了广泛的应用。高速异形插件机作为自动化生产设备中的一种&#xff0c;其核心部件之一就是一体化步进伺服电机。本文将详细介绍一体化步进伺服电机在高速异形插件机中的应用。 01.设备简介 高速异形插件机是一种…

我们为什么要写作?

为什么要写书是一个很难回答的问题&#xff0c;因为从不同的角度&#xff0c;会有不同的答案。 最近ChatGPT很火&#xff01;诸事不决&#xff0c;先问问ChatGPT&#xff0c;看看它是怎么回答的。 ChatGPT给出的答案还是比较全&#xff0c;虽然没有“一本正经的胡说八道”&…

PCB高频电路设计中的差分信号设计

目录 1、差分信号的定义 2、如何布置差分线路&#xff1f; 3、微带线和带状线的概念 4、布线中常用的匹配方法 1、差分信号的定义 什么是差分布线&#xff1a;差分布线主要是区别传统的信号线对应一根地线的信号传输方式&#xff0c;差分信号传输主要是两条线上都有信号传…

基本数据类型和引用数据类型的存储区别?

目录 1、存储位置的区别 2、变量赋值时的区别 ① 基本数据类型 ② 引用数据类型 3、小结 ① 声明变量时不同的内存地址分配 ② 不同的类型数据导致赋值变量时的不同 1、存储位置的区别 基本数据类型和引用数据类型存储在内存中的位置不同&#xff1a; ① 基本数据类型存…

20230420 | 977. 有序数组的平方、 209. 长度最小的子数组、59. 螺旋矩阵 II

1、977. 有序数组的平方 方法1&#xff1a;使用暴力法&#xff0c;一遍for&#xff0c;一次排序。这个时间复杂度是 O(n nlogn)&#xff0c; 可以说是O(nlogn)的时间复杂度。 class Solution {public int[] sortedSquares(int[] nums) {//先计算出平方for(int i0;i<nums.le…

Vulnhub项目:JANGOW 1.0.1

靶机地址&#xff1a;Jangow: 1.0.1 ~ VulnHub 渗透过程&#xff1a; kali ip&#xff1a;192.168.56.104&#xff0c;使用arp-scan -l查看到靶机ip192.168.56.118 对靶机进行端口探测&#xff0c;发现了21、80端口 访问80端口&#xff0c;发现site目录 点击进去后&#xff0…

HIVE SQL 进行 Join 和 group by的具体原理及分区方式

HIVE SQL 实现Join和group by 具体原理 1、JOIN 在map的输出value中为不同表的数据打上tag标记&#xff0c;在reduce阶段根据tag判断数据来源。MapReduce的过程如下&#xff1a; 2、 GROUP BY HIVE SQL 实现Join和group by 的分区原理 1、JOIN 在join操作中&#xff0c;两个…

【Java】哔哩哔哩编程题练习

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 每日随心练&#xff0c;望各位大佬喜欢&#xff0c;做法有很多种&#xff0c;以下是我个人的想法 目录 一、复数乘法 二、一年中的第几天 三、k个一组翻转链表 一、复数乘法 输入两个表示复…

电路中电容的作用

总体目录 电源设计中常见电容常见电容作用降压滤波高通滤波平滑输出电压 延时耦合去耦/退耦旁路 不常用电容作用调谐电容衬垫电容补偿电容中和电容稳频电容定时电容加速电容启动电容运转电容 电源设计中常见电容 安规电容 常见电容作用 https://www.bilibili.com/video/BV…

【K8S系列】深入解析控制器

目录 序言 1 基础介绍 1.1 前情提要 1.2 Kube-controller-manager介绍 1.3 控制器类型 2 使用介绍 2.1 控制循环 2.2 Deployment控制器 实际状态&#xff1a; 期望状态&#xff1a; PodTemplate&#xff1a; 3 总结 4 投票 序言 在你想要放弃的时候&#xff0c;想…

常见的上采样操作以及其Pytorch实现

文章目录 常见的上采样操作以及其Pytorch实现一、[插值](https://www.cnblogs.com/zhaozhibo/p/15024928.html)1.最近邻插值2.双线性插值3.双三次插值 二、反卷积三、sub-pixel Convolution四、其它方法1.[superpoint](https://arxiv.org/abs/1911.11763)使用方法2.待补充 常见…

Word 设置标题编号

用到了多级列表&#xff0c;所谓多级列表&#xff0c;就是为段落标上编号&#xff0c;并不一定就要对文字使用&#xff0c; 教程参考自Word 多级列表编号方法总结&#xff08;二&#xff09;——自定义编号 - 知乎 直接看我的就好了 假设我们有一个需求 类似于这样的三级标题…

【SpringBoot】面试必杀技-泰山陨石坠,SpringBoot中主启动类的run方法启动流程是什么?

开头导语由Chatgpt完成 当今互联网行业中&#xff0c;Java后端开发岗位的竞争异常激烈&#xff0c;对于面试者来说&#xff0c;掌握一些技巧和知识点将有助于脱颖而出。而对于SpringBoot框架的使用和运行机制&#xff0c;更是Java后端开发岗位中不可或缺的技能点之一。在Spring…

Python里的元组、列表和字典区别

列表&#xff1a;可更改、有序、可重复、元素可以是任何对象 列表示例&#xff1a;[1,a,[2,3]] 元组&#xff1a;不可更改、有序、可重复、元素可以是任何对象 元组示例&#xff1a;(b,1,[2,3]) 字典&#xff1a;可更改、无序、键不可重复、键不可变、值可以是任何对象&…

【Redis-面试题及持久化方案】Redis相关面试题(缓存穿透、缓存击穿、缓存雪崩) Redis两种持久化方案详情对比(RDB、AOF)

【Redis-面试题及持久化方案】Redis相关面试题&#xff08;缓存穿透、缓存击穿、缓存雪崩&#xff09; & Redis两种持久化方案详情对比&#xff08;RDB、AOF&#xff09; 1&#xff09;Redis 面试题1.1.高频面试题&#xff1a;缓存穿透、缓存击穿、缓存雪崩1.2.低频面试题&…

校友小程序定制开发 带我们回到那个学生时代

学生时代总是给人一种单纯美好的感觉&#xff0c;也会是每个人记忆深处最深刻的回忆&#xff0c;尤其是一起学习生活几年的同窗随着毕业不得不各奔东西&#xff0c;但是大家都对母校有着不一样的情怀&#xff0c;也想有一个什么东西能够把各个高校校友联系在一起。校友小程序开…

瑞芯微 Rockchip rknn 模型在线预编译

瑞芯微 Rockchip rknn 模型在线预编译 flyfish 主机Host环境 Distributor ID: Ubuntu Description: Ubuntu 22.04.2 LTS Release: 22.04 Codename: jammy开发板Target环境 RV1126一 主机连接开发板 用线连起来后&#xff0c;查看usb信息 没权限的提示 no permissions (…

微信小程序笔记(1)

小程序笔记 小程序一个页面为什么有四个文件&#xff1f; [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZVdz4I1W-1681982063887)(C:\Users\26387\AppData\Roaming\Typora\typora-user-images\image-20230419170019877.png)] wxss&#xff1a;写页…

对学历贬值的再观察

最近在求职市场上观察到这样一个现象&#xff0c;有些4年前硕士学历就能进入的职业&#xff0c;现在都要博士了&#xff0c;不免让人有一种4年书白读的感觉。所以想再观察一下学历贬值&#xff0c;平复一下心情&#xff08;不是&#xff09;。 学历贬值的原因&#xff1a;供大于…

day1 Flappy bird项目介绍

项目介绍 功能分析&#xff1a; 1、按下空格小鸟上升&#xff0c;不按下落&#xff1b; 2、搭建小鸟需要穿过的管道&#xff1b; 3、管道自动左移和创建&#xff1b; 4、小鸟触碰到管道游戏结束&#xff1b; 知识储备&#xff1a; 1、C语言&#xff1b; 2、数据结构 -…