【Linux】24.进程间通信(3)

news2025/2/7 10:34:02

文章目录

    • 3.6 systemv共享内存
      • 3.6.1 共享内存函数
      • 3.6.3 一个简单的共享内存代码实现
      • 3.6.4 一个复杂的共享内存代码实现
      • 3.6.4 key和shmid的主要区别:
    • 3.7 systemv消息队列(了解)
    • 3.8 systemv信号量(了解)
      • 进程互斥
      • 四个问题
      • 理解信号量


3.6 systemv共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

进程间通信的本质是:先让不同的进程,看到同一份资源。

72881d84255fc303b20a3ab2711e9853

如果要释放共享内存:要去除关联,释放共享内存

上面的操作都是进程直接做的吗?

不是。直接由操作系统来做。

共享内存的生命周期是随内核的。

用户不主动关闭,共享内存会一直存在。除非内核重启(用户释放)


生成IPC(进程间通信)的key值:

函数原型:

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

参数含义:

  1. pathname:一个已存在的文件路径
    • 必须是一个已存在的文件或目录的路径
    • 用于生成唯一的 key
    • 程序需要有该路径的访问权限
  2. proj_id:项目标识符
    • 项目标识符,用于区分不同的IPC资源
    • 只有低8位有效(1-255
    • 通常使用字符或数字

返回值:

  • 成功:成功:返回一个非负的 key 值
  • 失败:返回-1

3.6.1 共享内存函数

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,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

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

shmctl函数

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

981fea39a7c0c97a0551b9f2613d1954

  1. shmget - 创建/获取共享内存
int shmid = shmget(key, 1024, IPC_CREAT | 0666);
  • 相当于"申请"一块共享内存
  • 类比文件操作中的 open 创建文件
  • 返回共享内存标识符(shmid),用于后续操作
  1. shmat - 挂载/连接共享内存
void *addr = shmat(shmid, NULL, 0);
  • 将共享内存映射到进程的地址空间
  • 类比把硬盘上的文件加载到内存
  • 返回可以直接操作的内存指针
  1. shmdt - 断开连接
shmdt(addr);
  • 解除进程与共享内存的映射关系
  • 类比关闭打开的文件
  • 不会删除共享内存,只是当前进程不再使用
  1. shmctl - 控制共享内存
shmctl(shmid, IPC_RMID, NULL);  // 删除共享内存
  • 用于删除或管理共享内存
  • 类比文件的删除、权限修改等操作

3.6.3 一个简单的共享内存代码实现

写进程 (writer.cpp):

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

#define SHM_SIZE 1024

int main() {
    // 1. 生成key
    key_t key = ftok(".", 'x');
    // 这里的 "." 表示当前目录必须是一个存在且可访问的路径
    // 这里的 'x' 是一个字符,会被转换为8位整数,范围是1-255,只有低8位有效
    if(key == -1) {
        std::cout << "ftok失败" << std::endl;
        return 1;
    }

    // 2. 创建共享内存
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if(shmid == -1) {
        std::cout << "shmget失败" << std::endl;
        return 1;
    }

    // 3. 连接共享内存
    void* shmaddr = shmat(shmid, NULL, 0);
    if(shmaddr == (void*)-1) { //(void*)-1 是 shmat 失败时的返回值,等同于 MAP_FAILED 或 -1
        std::cout << "shmat失败" << std::endl;
        return 1;
    }

    // 4. 写入数据
    const char* message = "Hello from shared memory!";
    strcpy((char*)shmaddr, message); // shmaddr 是共享内存的起始地址
    // (char*) 是类型转换,将 void* 转换为 char*
    // strcpy 将字符串复制到共享内存中
    std::cout << "写入数据: " << message << std::endl;

    // 5. 分离共享内存
    shmdt(shmaddr);

    return 0;
}

读进程 (reader.cpp):

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

#define SHM_SIZE 1024

int main() {
    // 1. 生成相同的key
    key_t key = ftok(".", 'x');
    if(key == -1) {
        std::cout << "ftok失败" << std::endl;
        return 1;
    }

    // 2. 获取共享内存
    int shmid = shmget(key, SHM_SIZE, 0666);
    if(shmid == -1) {
        std::cout << "shmget失败" << std::endl;
        return 1;
    }

    // 3. 连接共享内存
    void* shmaddr = shmat(shmid, NULL, 0);
    if(shmaddr == (void*)-1) {
        std::cout << "shmat失败" << std::endl;
        return 1;
    }

    // 4. 读取数据
    std::cout << "读取数据: " << (char*)shmaddr << std::endl;

    // 5. 分离共享内存
    shmdt(shmaddr);

    // 6. 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}

使用方法:

  1. 编译:
g++ writer.cpp -o writer
g++ reader.cpp -o reader
  1. 运行:
# 终端1
./writer

# 终端2
./reader

主要函数说明:

  1. ftok(): 生成IPC键值
  2. shmget(): 创建或获取共享内存段
  3. shmat(): 连接共享内存段到进程地址空间
  4. shmdt(): 断开共享内存段连接
  5. shmctl(): 控制共享内存段(如删除)

执行时序:

写进程                     共享内存                      读进程
  |                          |                           |
  |                          |                           |
  |---ftok(".", 'x')         |                           |
  |生成key                    |                           |
  |                          |                           |
  |---shmget()               |                           |
  |创建共享内存--------------->|                           |
  |                          |                           |
  |---shmat()                |                           |
  |连接共享内存<-------------->|                           |
  |                          |                           |
  |---strcpy()               |                           |
  |写入数据------------------>|                           |
  |                          |                           |
  |---shmdt()                |                           |
  |断开连接------------------>|                           |
  |                          |                           |
  |                          |   ftok(".", 'x')----------|
  |                          |   生成相同的key             |
  |                          |                           |
  |                          |   shmget()----------------|
  |                          |<--获取共享内存              |
  |                          |                           |
  |                          |   shmat()-----------------|
  |                          |<->连接共享内存              |
  |                          |                           |
  |                          |   读取数据-----------------|
  |                          |-->读取内容                 |
  |                          |                           |
  |                          |   shmdt()-----------------|
  |                          |<--断开连接                 |
  |                          |                           |
  |                          |   shmctl()----------------|
  |                          |x--删除共享内存              |
  |                          |                           |

3.6.4 一个复杂的共享内存代码实现

makefile

.PHONY:all
all:processa processb

processa:processa.cc
	g++ -o $@ $^ -g -std=c++11
processb:processb.cc
	g++ -o $@ $^ -g -std=c++11

.PHONY:clean
clean:
	rm -f processa processb

log.hpp

#pragma once  // 防止头文件重复包含

// 包含必要的系统头文件
#include <iostream>
#include <time.h>
#include <stdarg.h>  // 用于可变参数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024  // 缓冲区大小

// 定义日志级别
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

// 定义日志输出方式
#define Screen 1      // 输出到屏幕
#define Onefile 2     // 输出到单个文件
#define Classfile 3   // 根据日志级别输出到不同文件

#define LogFile "log.txt"  // 默认日志文件名

class Log
{
    public:
    Log()
    {
        printMethod = Screen;  // 默认输出到屏幕
        path = "./log/";      // 默认日志路径
    }

    // 设置日志输出方式
    void Enable(int method)
    {
        printMethod = method;
    }

    // 将日志级别转换为字符串
    std::string levelToString(int level)
    {
        switch (level)
        {
            case Info:
                return "Info";
            case Debug:
                return "Debug";
            case Warning:
                return "Warning";
            case Error:
                return "Error";
            case Fatal:
                return "Fatal";
            default:
                return "None";
        }
    }

    // 根据不同的输出方式打印日志
    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
            case Screen:
                std::cout << logtxt << std::endl;
                break;
            case Onefile:
                printOneFile(LogFile, logtxt);
                break;
            case Classfile:
                printClassFile(level, logtxt);
                break;
            default:
                break;
        }
    }

    // 输出到单个文件
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }

    // 根据日志级别输出到不同文件
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level);
        printOneFile(filename, logtxt);
    }

    // 重载函数调用运算符,支持可变参数的日志打印
    void operator()(int level, const char *format, ...)
    {
        // 获取当前时间
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        // 格式化时间和日志级别信息
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        // 处理可变参数
        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 组合完整的日志文本
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

        printLog(level, logtxt);
    }
    
    ~Log()
    {
    }



    // void logmessage(int level, const char *format, ...)
    // {
    //     time_t t = time(nullptr);
    //     struct tm *ctime = localtime(&t);
    //     char leftbuffer[SIZE];
    //     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
    //              ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
    //              ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

    //     // va_list s;
    //     // va_start(s, format);
    //     char rightbuffer[SIZE];
    //     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
    //     // va_end(s);

    //     // 格式:默认部分+自定义部分
    //     char logtxt[SIZE * 2];
    //     snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

    //     // printf("%s", logtxt); // 暂时打印
    //     printLog(level, logtxt);
    // }

    private:
    int printMethod;       // 日志输出方式
    std::string path;      // 日志文件路径
};

// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);

//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }

//     va_end(s); //s = NULL
//     return sum;
// }

comm.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP__

// 包含必要的头文件
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>    // 系统IPC功能
#include <sys/shm.h>    // 共享内存
#include <sys/types.h>
#include <sys/stat.h>

#include "log.hpp"

using namespace std;

Log mylog;  // 全局日志对象

// 共享内存的大小一般建议是4096的整数倍
const int size = 4096; 
const string pathname="/home/ydk_108";  // 用于生成key的路径
const int proj_id = 0x6666;         // 项目ID

// 获取IPC key
key_t GetKey()
{
    key_t k = ftok(pathname.c_str(), proj_id);
    if(k < 0)
    {
        mylog(Fatal, "ftok error: %s", strerror(errno));
        exit(1);
    }
    mylog(Info, "ftok success, key is : 0x%x", k);
    return k;
}

// 获取共享内存的辅助函数
int GetShareMemHelper(int flag)
{
    key_t k = GetKey();
    int shmid = shmget(k, size, flag);
    if(shmid < 0)
    {
        mylog(Fatal, "create share memory error: %s", strerror(errno));
        exit(2);
    }
    mylog(Info, "create share memory success, shmid: %d", shmid);
    return shmid;
}

// 创建新的共享内存
int CreateShm()
{
    return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}

// 获取已存在的共享内存
int GetShm()
{
    return GetShareMemHelper(IPC_CREAT); 
}

#define FIFO_FILE "./myfifo"  // 命名管道文件路径
#define MODE 0664             // 文件权限

// 错误码枚举
enum
{
    FIFO_CREATE_ERR = 1,
    FIFO_DELETE_ERR,
    FIFO_OPEN_ERR
};

// 初始化类,用于创建和清理命名管道
class Init
{
public:
    Init()
    {
        // 先尝试删除已存在的管道文件
        unlink(FIFO_FILE);  // 忽略返回值,因为文件可能不存在
        // 创建命名管道
        int n = mkfifo(FIFO_FILE, MODE);
        if (n == -1)
        {
            perror("mkfifo");
            exit(FIFO_CREATE_ERR);
        }
    }
    ~Init()
    {
        // 删除命名管道
        int m = unlink(FIFO_FILE);
        if (m == -1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};

#endif

processa.cc

#include "comm.hpp"

extern Log mylog;

int main()
{
    Init init;  // 创建命名管道
    int shmid = CreateShm();  // 创建共享内存
    // 将共享内存映射到进程地址空间
    char *shmaddr = (char*)shmat(shmid, nullptr, 0);

    // 以只读方式打开命名管道
    int fd = open(FIFO_FILE, O_RDONLY);
    if (fd < 0)
    {
        mylog(Fatal, "error string: %s, error code: %d", strerror(errno), errno);
        exit(FIFO_OPEN_ERR);
    }

    struct shmid_ds shmds;
    while(true)
    {
        // 读取管道中的通知
        char c;
        ssize_t s = read(fd, &c, 1);
        if(s == 0) break;  // 写端关闭
        else if(s < 0) break;  // 读取错误

        // 直接从共享内存读取数据
        cout << "client say@ " << shmaddr << endl;
        sleep(1);

        // 获取并打印共享内存的状态信息
        shmctl(shmid, IPC_STAT, &shmds);
        cout << "shm size: " << shmds.shm_segsz << endl;
        cout << "shm nattch: " << shmds.shm_nattch << endl;
        printf("shm key: 0x%x\n",  shmds.shm_perm.__key);
        cout << "shm mode: " << shmds.shm_perm.mode << endl;
    }

    // 清理资源
    shmdt(shmaddr);  // 解除内存映射
    shmctl(shmid, IPC_RMID, nullptr);  // 删除共享内存
    close(fd);  // 关闭管道
    return 0;
}

processb.cc

#include "comm.hpp"

int main()
{
    int shmid = GetShm();  // 获取已存在的共享内存
    // 将共享内存映射到进程地址空间
    char *shmaddr = (char*)shmat(shmid, nullptr, 0);

    // 以只写方式打开命名管道
    int fd = open(FIFO_FILE, O_WRONLY);
    if (fd < 0)
    {
        mylog(Fatal, "error string: %s, error code: %d", strerror(errno), errno);
        exit(FIFO_OPEN_ERR);
    }

    while(true)
    {
        cout << "Please Enter@ ";
        // 读取用户输入并直接写入共享内存
        fgets(shmaddr, 4096, stdin);

        // 向管道写入一个字符,通知接收端
        write(fd, "c", 1);
    }

    // 清理资源
    shmdt(shmaddr);  // 解除内存映射
    close(fd);  // 关闭管道
    return 0;
}

打印:

c297c18f8ca1b2702ec33357f46ef20e

关于key

  1. key是一个数字,这个数字是几,不重要。关键在于它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识。

  2. 第一个进程可以通过kev创建共享内存,第二个之后的进程,只要拿着同一个key就可以和第一个进程看到同一个共享内存了。

  3. 对于一个已经创建好的共享内存,key在哪?

    key在共享内存的描述对象中。

  4. 第一次创建的时候,必须有一个key了。怎么有?

  5. key 类似路径唯一


3.6.4 key和shmid的主要区别:

  1. 基本概念

    key:是一个用户定义的值,用来标识共享内存段的访问权限,类似于文件路径名

    shmid:是系统分配的共享内存段标识符,是系统内部使用的唯一标识符

  2. 使用时机

    key:在创建或获取共享内存时使用

    shmid:在共享内存创建后由系统返回,后续操作都使用shmid

  3. 代码示例

#include <sys/shm.h>

// 使用key创建共享内存
key_t key = ftok("/tmp", 'A');  // 创建key
int shmid = shmget(key, 1024, IPC_CREAT | 0666); // 用key获取shmid

// 后续操作使用shmid
void *addr = shmat(shmid, NULL, 0);  // 连接共享内存
shmctl(shmid, IPC_RMID, NULL);  // 删除共享内存
  1. 关系

    一个key可以对应一个shmid

    key是用户层面的标识

    shmid是系统层面的标识

  2. 生命周期

    key:可以重复使用

    shmid:随共享内存段的存在而存在,删除后失效


3.7 systemv消息队列(了解)

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

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

特性方面:IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

通过消息队列想让AB进行通信那么首先要让不同进程看到同一份资源。

  1. 必须让不同进程看到同一个队列

  2. 允许不同的进程,向内核中发送带类型的数据块(通过类型来区分数据块是属于谁的)

A进程可以把它的数据块放到队列中,B进程可以把它的数据块放到队列中。

A进程就可以从队列中拿B进程给A进程发的数据块,反之亦然。

可以让A进程 <--以数据块的形式发送数据--> B进程。

2afaf5fef54063425c71bea5f3ab0008


3.8 systemv信号量(了解)

信号量主要用于同步和互斥的。什么是同步和互斥?

进程互斥

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

系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。

在进程中涉及到互斥资源的程序段叫临界区。

特性方面:IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核


四个问题

当我们的A正在写入,写入了一部分,就被B拿走了,导致双方发和收的数据不完整 – 数据不一致问题

  1. A B看到的同一份资源,共享资源,如果不加保护,会导致数据不一致问题

  2. 我们可以通过“加锁”形成互斥访问 – 任何时刻,只允许一个执行流访问共享资源 – 互斥

  3. 共享的,任何时刻只允许一个执行流访问(就是执行访问代码)的资源我们一般称为:临界资源(一般是操作系统和用户维护的内存空间)(管道也是临界资源)

  4. 举例:100行代码,5~10行代码才在访问临界资源。 我们访问临界资源的代码在:临界区


理解信号量

信号量的本质是一个计数器。

描述临界资源数量的多少。

  1. 申请计数器成功,就表示我具有访问资源的权限了

  2. 申请了计数器资源,我当前访问我要的资源了吗?没有。申请了计数器资源是对资源的预订机制

  3. 计数器可以有效保证进入共享资源的执行流的数量

  4. 所以每一个执行流,想访问共享资源中的一部分的时候,不是直接访问,而是先申请计数器资源。

程序员把这个"计数器",叫做信号量。

申请信号量,本质是对计数器--,P操作

释放资源,释放信号量,本质是对计数器进行++操作,V操作

申请和释放PV操作是原子性操作。

要么不做,要做就做完 — 两态的。没有“正在做”这样的概念。

信号量本质是一把计数器,PV操作,原子的。

执行流申请资源,必须先申请信号量资源,得到信号量之后,才能访问临界资源。

信号量值1,0两态的,二元信号量,就是互斥功能

申请信号量的本质:是对临界资源的预订机制。

信号量凭什么是进程间通信的一种?

  1. 通信不仅仅是通信数据,双方互相协同也是。

  2. 要协同,本质也是通信,信号量首先要被所有的通信进程看到。

mmap函数 – 也是共享内存。(仅作了解)

后面学习的信号和这里的信号量没有任何关系。

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

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

相关文章

【自然语言处理】TextRank 算法提取关键词(Python实现)

文章目录 前言PageRank 实现TextRank 简单版源码实现jieba工具包实现TextRank 前言 TextRank 算法是一种基于图的排序算法&#xff0c;主要用于文本处理中的关键词提取和文本摘要。它基于图中节点之间的关系来评估节点的重要性&#xff0c;类似于 Google 的 PageRank 算法。Tex…

如何评价镜头的好坏?光学镜头的一种评价标准

1光学传递函数MTF MTF是什么&#xff1f; 光学传递函数&#xff08;optical transfer function&#xff09;是指以空间频率为变量&#xff0c;表征成像过程中调制度和横向相移的相对变化的函数。光学传递函数是光学系统对空间频谱的滤波变换。一个非相干照明的光学成像系统&a…

openGauss 3.0 数据库在线实训课程1:学习数据库状态查看

openGauss数据库状态查看 前提 我正在参加21天养成好习惯| 第二届openGauss每日一练活动 课程详见&#xff1a;openGauss 3.0.0数据库在线实训课程 学习目标 学习从操作系统层面和使用openGauss工具查看数据库的状态、版本和数据文件目录。 课程作业 gs_ctl是openGauss提…

Stable Diffusion的入门介绍和使用教程

Stable Diffusion是一个文本到图像的潜在扩散模型&#xff0c;由CompVis、StabilityAI和LAION的研究人员和工程师创建。它使用来自LAION-5B数据库子集的512x512图像进行训练。使用这个模型&#xff0c;可以生成包括人脸在内的任何图像&#xff0c;因为有开源的预训练模型&#…

记一次框架利用接管学工系统

视频教程在我主页简介或专栏里 链接&#xff1a;观看更多 Springboot actuator &#xff08;1&#xff09;某学院学工管理系统存在Springboot actuator未授权,泄露了很多接口地址&#xff0c;其他接口就不过多介绍了&#xff0c;这里具体讲述这次利用到的httptrace和jolokia两…

低代码提升交付效率的公式计算

低&#xff08;无&#xff09;代码平台&#xff08;后统称“低代码”&#xff09;能够提升数字化应用建设、交付效率&#xff0c;已经成为IT从业人员的共识。目前&#xff0c;大部分CIO/CDO都能清晰定位和认知低代码的特点和作用。但仍然有人认为&#xff0c;使用了低代码工具软…

全程Kali linux---CTFshow misc入门(25-37)

第二十五题&#xff1a; 提示&#xff1a;flag在图片下面。 直接检查CRC&#xff0c;检测到错误&#xff0c;就直接暴力破解。 暴力破解CRC的python代码。 import binascii import struct def brute_force_ihdr_crc(filename): # 读取文件二进制数据 with open(filen…

Axure大屏可视化动态交互设计:解锁数据魅力,引领决策新风尚

可视化组件/模板预览&#xff1a;https://8dge09.axshare.com 一、大屏可视化技术概览 在数据驱动决策的时代&#xff0c;大屏可视化技术凭借直观、动态的展示方式&#xff0c;已成为众多行业提升管理效率和优化决策过程的关键工具。它能够将复杂的数据转化为易于理解的图形和…

Verilog语言学习总结

Verilog语言学习&#xff01; 目录 文章目录 前言 一、Verilog语言是什么&#xff1f; 1.1 Verilog简介 1.2 Verilog 和 C 的区别 1.3 Verilog 学习 二、Verilog基础知识 2.1 Verilog 的逻辑值 2.2 数字进制 2.3 Verilog标识符 2.4 Verilog 的数据类型 2.4.1 寄存器类型 2.4.2 …

软件工程-数据流图DFD

数据流图&#xff08;DFD&#xff09;是一种图形化技术&#xff0c;它描绘信息流和数据从输入移动到输出的过程中经受的变换。 数据流图是系统逻辑功能和图形表示&#xff0c;即使不是专业的计算机人员也容易理解它&#xff0c;因此是分析员与用户之间极好的通信工具。 设计数…

Java数据结构与算法之“树”

目录 一、什么是树 ​编辑 二、树的相关组成 1. 常用名词 2.需要了解的名词 三、树的分类 &#xff08;一&#xff09;初级树 1.普通树 2.二叉树 &#xff08;二&#xff09;中级树 1.哈夫曼树HuffmanTree 2.二叉搜索树BST 3.平衡二叉树AVL &#xff08;三&#x…

基于Python的智能物流路径优化算法研究与应用

基于Python的智能物流路径优化算法研究与应用 摘要 随着电商行业的迅猛发展&#xff0c;物流配送的效率和成本成为影响企业竞争力的关键因素。本论文聚焦于基于Python语言实现智能物流路径优化算法的研究。通过对经典路径优化算法如Dijkstra算法、A*算法等的深入分析&#xff…

Origin2024 软件安装步骤与百度网盘

软件简介&#xff1a; Origin 2024是一款功能强大的科学绘图与数据分析软件&#xff0c;广泛应用于科研和工程领域&#xff0c;支持多种图形绘制、数据分析功能以及便捷的数据导入和管理。 网盘链接: https://pan.baidu.com/s/1MNQG2pD802LWxuIN40JfeA?pwdc85q 提取码:c85…

【算法应用】Alpha进化算法求解二维栅格路径规划问题

目录 1.算法原理2.二维路径规划数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 Alpha进化&#xff1a;一种具有进化路径自适应和矩阵生成的高效进化算法 2.二维路径规划数学模型 栅格法模型最早由 W.E. Howden 于 1968 年提出&#xff0c;障碍物的栅格用黑色表示&#…

嵌入式八股文面试题(一)C语言部分

1. 变量/函数的声明和定义的区别&#xff1f; &#xff08;1&#xff09;变量 定义不仅告知编译器变量的类型和名字&#xff0c;还会分配内存空间。 int x 10; // 定义并初始化x int x; //同样是定义 声明只是告诉编译器变量的名字和类型&#xff0c;但并不为它分配内存空间…

Redis企业开发实战(二)——点评项目之商户缓存查询

目录 一、缓存介绍 二、缓存更新策略 三、如何保证redis与数据库一致性 1.解决方案概述 2.双写策略 3.双删策略 3.1延迟双删的目的 4.数据重要程度划分 四、缓存穿透 (一)缓存穿透解决方案 (二)缓存穿透示意图 五、缓存雪崩 (一)缓存雪崩解决方案 (二)缓存雪崩…

RK3568中使用QT opencv(显示基础图像)

文章目录 一、查看对应的开发环境是否有opencv的库二、QT使用opencv一、查看对应的开发环境是否有opencv的库 在开发板中的/usr/lib目录下查看是否有opencv的库: 这里使用的是正点原子的ubuntu虚拟机,在他的虚拟机里面已经安装好了opencv的库。 二、QT使用opencv 在QT pr…

C++模板编程——完美转发与可变参函数模板

1 基础概念 首先介绍几个概念&#xff1a; 假设现在有A、B、C三个函数。 直接调用&#xff1a;在A函数中调用C就叫做直接调用&#xff0c;不拐弯抹角。转发&#xff1a;在A函数中调用B函数&#xff0c;在B函数调用C函数&#xff0c;这就叫做转发。这种情况下&#xff0c;B函数…

蓝桥杯单片机(十)PWM脉宽调制信号的发生与控制

模块训练&#xff1a; 一、PWM基本原理 1.占空比 2.脉宽周期与占空比 当PWM脉宽信号的频率确定时&#xff0c;脉宽周期也确定了&#xff0c;此时改变占空比即可。当利用PWM脉宽周期改变LED灯的亮度时&#xff0c;灯是低电平亮&#xff0c;所以将低电平占空比改成10%即可实现…

Redis --- 使用HyperLogLog实现UV(访客量)

UV 和 PV 是网站或应用数据分析中的常用指标&#xff0c;用于衡量用户活跃度和页面访问量。 UV (Unique Visitor 独立访客)&#xff1a; 指的是在一定时间内访问过网站或应用的独立用户数量。通常根据用户的 IP 地址、Cookies 或用户 ID 等来唯一标识一个用户。示例&#xff1…