Linux之进程间通信(二)

system V

system V共享内存内核中专门设计的通信的方式,  粗粒度划分操作系统分为进程管理, 内存管理, 文件系统, 驱动管理.., 粒度更细地分还有 进程间通信模块.

对于操作系统, 通信的场景有很多, 有以传送数据, 快速传送数据, 传送特定数据块, 进程间协同与控制以目的, 它们在接口实现上都不相同, 所以把操作系统中通信的方式聚集在一块, 接口统一之后形成了限于本主机通信的一种模式叫systemV

管道不属于system V, 它们是复用操作系统源代码, 属于比较原始的通信方式.


进程间通信的前提:必须让不同的进程看到同一份资源(必须由OS提供)

共享内存

原理

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

共享内存是专门设计用于IPC的, 它与进程地址空间共享区有关.

共享内存作为一种通信方式, 所有关联了这块内存的进程都可以使用它, 这块内存不再是只属于一个进程的.

1. 操作系统中一定会同时存在很多的共享内存, 所以共享内存也要被操作系统管理,
要认识到开辟一块空间的内存占用应当是物理内存空间共享内存的相关属性. 操作系统管理共享内存属性就是对其数据结构进行增删查改, 从而对它对应的物理内存空间进行实时监控管理.

共享内存结构: 

struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
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 */
           };

2. 因为操作系统内一定会同时存在许多共享内存, 那这么多的共享内存中如何保证 A进程创建的共享内存 需要和A通信的进程, 看到的是同一份共享内存呢?

凡是被创建共享内存, 相关属性结构体中必须有一个能体现出该共享内存的唯一性的数据,这个数据就是key. key不光是为了保证共享内存的唯一性, 也是为了通过 key 能够让其他进程识别该共享内存, 从而不同的进程才能选择特定的共享内存进行通信.

key被封装在上面的struct ipc_perm结构体内.

关于唯一性标识, 除了key还有一个叫shmid,  在应用这个共享内存的时候, 我们使用shmid来进行操作共享内存,类似文件操作的fd, 而 key 不要在应用层使用, 只用来在内核中标识shm的唯一性! 类似inode.

关于 key 和 shmid 需要介绍一下相关系统接口: 


系统接口

ftok

功能:用 路径名 和 项目标识符 转换为唯一标识符key返回

函数原型: key_t ftok(const char *pathname, int proj_id);

参数:

  • pathname: 文件路径名
  • proj_id: 项目标识符
  • 返回值: 标识某一个共享内存的key值

key的作用: 这里的key用于形成共享内存的唯一标识shmid, 而这个key的生成规则是通过路径名和项目标识符生成的, 所以对于同一个共享内存的使用者就可以通过规定这两个参数都能得到同样的key, 从而通过shmget创建/获取同样的共享内存.

shmget 

功能: 用来创建共享内存
原型: int shmget(key_t key, size_t size, int shmflg);
参数:

  • key: 这个共享内存段名字
  • size: 共享内存大小, 建议为4096的倍数
  • shmflg: 由九个权限标志构成, 它们的用法和创建文件时使用的mode模式标志是一样的
  • 返回值:成功返回一个非负整数, 即该共享内存段的标识码;失败返回-1 

 创建共享内存需要用到这两个标志:

首先要明确, shmget接口可以实现创建和使用共享内存,

1. 单独传入IPC_CREAT, 共享内存 存在就返回, 不存在就创建并返回

2. 传入IPC_CREAT|IPC_EXCL, 共享内存 存在就报错, 不存在就创建并返回, 注意IPC_EXCL单独传入没有意义, 和IPC_CREAT一起传入才有意义, 这两个标志位保证了创建的共享内存是全新的.

3. 传入0, 共享内存存在就返回, 不存在就报错.

 shmat(at = attach)

功能: 将共享内存段连接到进程地址空间
原型: void * shmat(int shmid, const void *shmaddr, int shmflg);
参数:

  • shmid: 共享内存标识
  • shmaddr: 指定连接的地址
  • shmflg: 它的两个可能取值是 SHM_RND 和 SHM_RDONLY
  • 返回值: 成功返回一个指针, 指向共享内存首地址; 失败返回-1

这个返回值和 malloc 是类似的, 都可以强转为指定类型的地址, 但是malloc是在堆区开辟一块空间, 并不是共享内存.

shmdt (dt = detach)

功能: 将共享内存段与当前进程脱离
原型: int shmdt(const void *shmaddr);
参数:

  • shmaddr: 由shmat所返回的指针
  • 返回值:成功返回0; 失败返回-1

注意: 将共享内存段与当前进程脱离不等于删除共享内存段

shmctl

功能: 用于控制共享内存
原型: int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:

  • shmid: 由shmget返回的共享内存标识码.
  • cmd: 将要采取的动作 (有三个可取值).
  • buf: 指向一个保存着共享内存的模式状态和访问权限的数据结构.
  • 返回值: 成功返回0;失败返回-1.

共享内存的生命周期:

共享内存的生命周期不会随着内存的释放而结束, 也就是说程序结束后如果没有用shmctl删除共享内存, 共享内存依然存在. 只要系统不重启或显式地删除该共享内存, 它就会一直存在, 一个进程退出, 其他进程仍然可以通过 shmat连接到共享内存并使用它, 直到调用 shmctl 删除共享内存为止.

代码

实现一个server端和client端, server作为读端每隔两秒打印共享内存中的内容, client每隔一秒进行写入: 

 comm.hpp

#include <iostream>
#include <cstring>
#include <cerrno>
const char* pathName = "/home/zzy/linux_system_programing/inter-process_communication";
const int project_id = 0x11223344;
// 共享内存的大小,强烈建议设置成为n*4096
const int size = 4096;

//创建一个key
key_t GetKey()
{
    key_t key = ftok(pathName, project_id);
    if(key < 0)
    {
        std::cerr << "errno:" << errno <<  ", errno string:" << strerror(errno)<<std::endl;
        exit(1);
    }
    return key;
}

//将key转换为0x开头字符串
std::string ToHex(key_t key)
{
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "0x%x", key);
    return buffer;
}

//子函数
int CreateShmHelper(key_t key, int shmflg)
{
    int shmid = shmget(key, size, shmflg);
    if(shmid < 0)
    {
        std::cerr << "errno:" << errno <<  ", errno string:" << strerror(errno)<<std::endl;
        exit(2);
    }
    return shmid;
}

//创建共享内存
int CreateShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT|IPC_EXCL|0644);
}

//获取共享内存
int GetShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT);//传0也可以
}

server.cc 

#include <iostream>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"
using namespace std;

int main()
{
    key_t key = GetKey();
    cout << "key: " << ToHex(key) << endl;

    // key vs shmid
    // shmid: 应用这个共享内存的时候,我们使用shmid来进行操作共享内存,类似文件操作的FILE*
    // key: 不要在应用层使用,只用来在内核中标识shm的唯一性! 类似文件操作的fd
    int shmid = CreateShm(key);
    cout << "shmid: " << shmid << endl;

    char* s = (char*)shmat(shmid, nullptr, 0);
    cout << "shm attach done: " << shmid << endl;

    while(true)
    {
        cout << s << endl;
        sleep(2);
    }

    //取消共享内存与进程地址空间的映射
    shmdt(s);
    cout << "shm dettach done: " << shmid << endl;
    sleep(3);

    //删除共享内存
    shmctl(shmid, IPC_RMID, nullptr);
    cout << "shm remove done " << shmid << endl;
    return 0;
}

client.cc 

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"
using namespace std;

int main()
{
    key_t key = GetKey();
    cout << "key: " << ToHex(key) << endl;
    
    int shmid = GetShm(key);
    cout << "shmid: " << shmid << endl;

    char* s = (char*)shmat(shmid, nullptr, 0);
    cout << "shm attach done: " << shmid << endl;

    for(char c = 'a'; c<='z'; c++)
    {
        s[c-'a'] = c;
        cout << "write " << c << " done" <<endl;
        sleep(1);
    }

    shmdt(s);
    cout << "shm dettach done: " << shmid << endl;

    return 0;
}

由结果可以看到, 当server端启动后, client也启动然后与shm连接, 并每隔1秒向shm中发送数据, 而server每隔2秒读取shm中的内容 :

共享内存的同步方式: 不提供同步机制, 共享内存是直接裸露给所有的使用者使用的, 一定要注意共享内存的使用安全问题. 

借助管道的同步机制, 可以让共享内存实现同步:

comm.hpp添加新建管道文件的函数: 

//创建管道
bool MakeFifo()
{
    int fd = mkfifo(filename.c_str(), 0666);
    if(fd < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        return false;
    }
    std::cout << "mkfifo success... read" << std::endl;
    return true;
}

 server.cc借助类修改了一下初始化和清理资源的方式, 然后在while循环中读取管道传来的内容以实现同步:

#include <iostream>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"
using namespace std;

class Init
{
public:
    Init()
    {
        bool r = MakeFifo();
        if (!r)
            return;

        key_t key = GetKey();
        cout << "key: " << ToHex(key) << endl;

        // key vs shmid
        // shmid: 应用这个共享内存的时候,我们使用shmid来进行操作共享内存,类似文件操作的FILE*
        // key: 不要在应用层使用,只用来在内核中标识shm的唯一性! 类似文件操作的fd
        shmid = CreateShm(key);
        cout << "shmid: " << shmid << endl;

        s = (char *)shmat(shmid, nullptr, 0);
        cout << "shm attach done: " << shmid << endl;

        fd = open(filename.c_str(), O_RDONLY);
    }

    ~Init()
    {
        // 取消共享内存与进程地址空间的映射
        shmdt(s);
        cout << "shm dettach done: " << shmid << endl;
        // 删除共享内存
        shmctl(shmid, IPC_RMID, nullptr);
        cout << "shm remove done " << shmid << endl;

        close(fd);
        unlink(filename.c_str());
    }

public:
    char *s;
    int shmid;
    int fd;
};

int main()
{
    Init init;

    while (true)
    {
        int code = 0;
        ssize_t n = read(init.fd, &code, sizeof(code));
        if(n > 0)
        {
            cout << init.s << endl;
            //sleep(2);
        }
        else if(n == 0)
        {
            break;
        }
    }

    return 0;
}

client.cc 向共享内存写入一个字符就向管道传入内容通知server可以读取数据了(同步): 

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"
using namespace std;

int main()
{
    key_t key = GetKey();
    cout << "key: " << ToHex(key) << endl;
    
    int shmid = GetShm(key);
    cout << "shmid: " << shmid << endl;

    char* s = (char*)shmat(shmid, nullptr, 0);
    cout << "shm attach done: " << shmid << endl;
    int fd = open(filename.c_str(), O_WRONLY);

    for(char c = 'a'; c<='z'; c++)
    {
        s[c-'a'] = c;
        cout << "write " << c << " done" <<endl;
        //通知对方
        int code = 1;
        ssize_t n = write(fd, &code, sizeof(code));
        sleep(1);
    }

    shmdt(s);
    cout << "shm dettach done: " << shmid << endl;
    close(fd);

    return 0;
}

这次可以发现 client 向 shm 写入一个数据, 就发送一次接收的指令, server就输出一次共享内存的内容, 完成了同步. 

关于共享内存的内容如何清理:

shm只涉及进程向 shm 中写入和读取内容, 而 shm 中的内容并不会因为内容的读取就被移除, 所以shm的内容需要用户自己清理, 如何清理? 可以规定shm的前8/16个字节存放两个写入和读取指针, 通过两个指针对shm的内容进行维护.

 关于shmctl接口查看共享内存结构体:

上面使用过IPC_RMID删除共享内存, 现在用 IPC_STAT 查看shmid_ds中的内容:

#include <iostream>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "comm.hpp"
using namespace std;

class Init
{
public:
    Init()
    {
        key_t key = GetKey();
        cout << "key: " << ToHex(key) << endl;

        // key vs shmid
        // shmid: 应用这个共享内存的时候,我们使用shmid来进行操作共享内存,类似文件操作的FILE*
        // key: 不要在应用层使用,只用来在内核中标识shm的唯一性! 类似文件操作的fd
        shmid = CreateShm(key);
        cout << "shmid: " << shmid << endl;

        s = (char *)shmat(shmid, nullptr, 0);
        cout << "shm attach done: " << shmid << endl;     
    }

    ~Init()
    {
        // 取消共享内存与进程地址空间的映射
        shmdt(s);
        cout << "shm dettach done: " << shmid << endl;
        // 删除共享内存
        shmctl(shmid, IPC_RMID, nullptr);
        cout << "shm remove done " << shmid << endl;
    }

public:
    char *s;
    int shmid;
    int fd;
};

int main()
{
    Init init;

    struct shmid_ds ds;
    shmctl(init.shmid, IPC_STAT, &ds);
    std::cout << ToHex(ds.shm_perm.__key) << std::endl;
    std::cout << ds.shm_segsz << std::endl;
    std::cout << ds.shm_atime << std::endl;
    std::cout << ds.shm_cpid << std::endl;
    std::cout << ds.shm_nattch << std::endl;

    return 0;
}

总结:

缺点:

共享内存的同步方式, 不会提供同步机制, 共享内存是直接裸露给所有的使用者使用的, 一定要注意共享内存的使用安全问题. 

优点:

1. 共享内存是所有进程间通信速度最快的

2. 共享内存可以提供较大的空间

第二点进行具体说明:

因为拷贝次数少, 共享内存是所有进程间通信方式中速度最快的. 在同样的代码下, 考虑键盘输入显示器输出(不考虑printf和scanf的缓冲区). 对比数据通过 共享内存 与 管道 通信的拷贝次数.

首先要明确, 凡事涉及到数据的迁移, 都是拷贝.

管道通信会经过四次拷贝: 输入文件(键盘)->用户缓冲区->管道文件缓冲区(内核空间)->用户缓冲区->输出文件(显示器)

共享内存通信只需要两次拷贝:输入文件(键盘)->共享内存->输出文件(显示器)


消息队列

功能:

  • 消息队列提供了一个从一个进程向另外一个进程发送一个数据块的方法, 消息队列和共享内存在使用上有一定差别, 但是它们的共享机制是一样的.
  • 每个数据块都被认为是有一个类型, 接收者进程接收的数据块可以有不同的类型值 

特性: 

同共享内存一样, 消息队列也属于systemV, 而IPC资源必须删除, 否则不会自动清除, 除非重启,所以system V IPC资源的生命周期内核 


消息队列的相关接口和共享内存都是相似的, 因为都属于systemV:

系统接口

msgget 

功能: 用来创建消息队列
原型: int msgget(key_t key, int shmflg);
参数:

  • key: 这个消息队列段名字
  • shmflg: 和共享内存一样
  • 返回值:成功返回一个非负整数, 即该消息队列段的标识码;失败返回-1 
msgctl 

功能: 用于控制消息队列
原型: int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:

  • msqid: 由msgget返回的共享内存标识码.
  • cmd: 将要采取的动作 
  • buf: 指向一个保存着消息队列的模式状态和访问权限的数据结构.
  • 返回值: 成功返回0;失败返回-1.
 msgsnd

功能: 用于消息队列发消息

原型: int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数:

msqid:msgget的返回值

msgp: 要发送的数据块, 其中包含了数据的类型和内容, 需要用户自己去定义

msgsz: 数据块大小

msgflg: 

msgrcv 

功能: 接收消息队列的消息

原型: ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数:

msgflg:

IPC_NOWAIT:如果没有符合条件的消息可用,不阻塞进程,而是立即返回 EAGAIN 错误

MSG_EXCEPT: msgtype大于0的前提下, 接收除msgtpe以外类型的所有消息

msgtyp:

  • 如果 msgtyp 大于 0,则 msgrcv 函数将接收消息队列中类型字段等于 msgtyp 的第一条消息。
  • 如果 msgtyp 等于 0,则 msgrcv 函数将接收消息队列中的第一条消息。
  • 如果 msgtyp 小于 0,则 msgrcv 函数将接收消息队列中类型字段小于或等于 msgtyp 绝对值的第一条消息

更多可以查看手册

void MsgQueue()
{
    //创建消息队列
    key_t key = GetKey();
    std::cout << "key: " << ToHex(key) << std::endl;

    int msgid = msgget(key, IPC_CREAT|IPC_EXCL);
    std::cout << "msgid: " << msgid << std::endl;

    //读取消息队列结构体
    struct msqid_ds ds; 
    msgctl(msgid, IPC_STAT, &ds);
    std::cout << ToHex(ds.msg_perm.__key) << std::endl;
    std::cout << ds.msg_qbytes << std::endl;
    sleep(10);

    //删除消息队列
    msgctl(msgid, IPC_RMID, nullptr);

}

运行结果: 

ipcs -q 

不同于共享内存, 这里内核里的key和我们传入key是不一样的.


信号量

信号量的本质是一个计数器, 用于保护共享资源. 信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥

多个执行流看到的同一份资源(公共资源), 在并发访问时很有可能会发生数据不一致的问题, 所以公共资源就需要被保护起来, 就有了互斥与同步. (匿名管道, 命名管道, 消息队列都由OS提供了保护措施, 而共享内存需要用户自己保证安全, 比如上面代码用管道实现同步)

互斥: 任何一个时刻只允许一个执行流(进程)访问公共资源, 加锁完成

同步: 多个执行流执行的时候, 按照一定的顺序执行.

原子性: 一个操作或者一系列操作要么全部执行成功, 要么全部不执行不存在中间状态或者部分执行的情况.

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

临界区: 访问该临界资源的代码, 维护临界资源, 其实就是在维护临界区

如何理解信号量?

举个例子, 比如看电影

每个电影院的座位都是有限的, 如果我们买了票, 即使不去使用这个座位, 这个座位也已经预定为我们的座位, 电影院和内部的座位是多个人共享的资源--公共资源, 我们买票的本质, 则是对资源的预定机制. 而一场电影的票的数量是有限的, 所以就需要设置一个计数器表示公共资源的个数, 买了一张票, 计数器就减一;退一张票, 计数器就加1, 如果计数器为0, 则买票失败.

信号量:

表示资源数目的计数器, 每一个执行流想要访问公共资源内部的某一份资源, 不应该让执行流直接访问, 而是先申请信号量资源, 申请成功就对信号量计数器做--操作, 申请不成功, 执行流将被挂起阻塞.

本质上, 只要--成功, 就完成了对资源的预定机制.

 假如一份资源的信号量为1, 它被称为二元信号量, 也叫互斥锁, 完成资源的互斥功能.

 所以如果一份共享内存整体只想被一个进程使用,可以使用二元信号量,以信号量的方式实现加锁解锁;如果所有进程都只想使用公共资源的一部分,比如一块16kb的共享内存每个进程只使用局部的一部分比如1kb,所以信号量可以设置为16,每个进程想访问这块共享内存首先要去申请信号量,申请成功才能使用,不成功则阻塞。

关于信号量的细节问题:

关于信号量的描述只是以整数计数器的形式去描述,但是其实并不是。

1. 每个进程访问共享资源都要先申请信号量意味着每个进程都得先看到同一个信号量资源,这只能由OS提供,所以信号量就也属于IPC体系, 不仅仅进程间通信属于IPC体系, 保证通信的安全也属于.

2.  信号量本质也是公共资源, 信号量是为了保护公共资源的, 但是谁去保护信号量呢, 所以对于信号量内部计数器的++或--操作, 必须是原子性的! (具体在多线程部分说明)

所以原子性的申请资源(--)的操作, 称为P; 原子性的释放资源(++)的操作, 称为V

3.  对于进程挂起/阻塞如何理解? 对于单个信号量, 简单地理解其结构为struct sem{int count; task_struct* wait_queue;} , 当信号量申请失败, 把进程设为阻塞状态并加入到对应的阻塞队列即可.

 


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

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

相关文章

SystemUI GlobalActions plugin解析

com.android.systemui.action.PLUGIN_GLOBAL_ACTIONS 系统的默认实现为GlobalActionsImpl: 是谁发送了showShutdownUi指令&#xff1f; GlobalActionsImpl 是通过inject的方式创建的 GlobalActionsComponent是一个system UI services&#xff0c;配置在config.xml中&#xff…

Docker容器:网络模式与资源控制

目录 一、Docker 网络模式 1、Docker 网络实现原理 2、Docker 网络模式概述 2.1 Host 模式 2.2 Container 模式 2.3 None 模式 2.4 Bridge 模式 2.5 自定义网络&#xff08;user-defined network&#xff09; 3、配置 docker 网络模式 3.1 查看网络基础命令 3.1.1 查…

“怡宝”冲刺港股,饮用水基本盘稳如磐石

最近&#xff0c;饮用水市场异常热闹。 先是“怡宝”所属的华润饮料正式向港交所提交上市申请。随即&#xff0c;多名农夫山泉员工在朋友圈发文“推出绿瓶纯净水”&#xff0c;撞脸怡宝经典包装。“怡宝”遭遇奇袭的背后&#xff0c;是双方持续“交锋”的多年&#xff0c;随着…

Vue从入门到精通-01-Vue的介绍和vue-cli

MVVM模式 Model&#xff1a;负责数据存储 View&#xff1a;负责页面展示 View Model&#xff1a;负责业务逻辑处理&#xff08;比如Ajax请求等&#xff09;&#xff0c;对数据进行加工后交给视图展示 关于框架 为什么要学习流行框架 1、企业为了提高开发效率&#xff1a;…

【Harmony3.1/4.0】笔记三-计算器

概念 网格布局是由“行”和“列”分割的单元格所组成&#xff0c;通过指定“项目”所在的单元格做出各种各样的布局。网格布局具有较强的页面均分能力&#xff0c;子组件占比控制能力&#xff0c;是一种重要自适应布局&#xff0c;其使用场景有九宫格图片展示、日历、计算器等…

python-pytorch 如何使用python库Netron查看模型结构(以pytorch官网模型为例)0.9.2

Netron查看模型结构 参照模型安装Netron写netron代码运行查看结果需要关注的地方 2024年4月27日14:32:30----0.9.2 参照模型 以pytorch官网的tutorial为观察对象&#xff0c;链接是https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial.html 模型代…

基于Springboot的新生宿舍管理系统

基于SpringbootVue的新生宿舍管理系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 公告信息管理 院系管理 班级管理 学生管理 宿舍信息管理 宿舍安排管理…

清华军团推出中国首个对标Sora的视频大模型Vidu,扒一扒它背后的模型架构

就在前天&#xff0c;Vidu 在 2024 中关村论坛年会之中横空出世。 伴随着“中国首个”&#xff0c;“Sora 级视频模型”&#xff0c;“模拟真实的物理世界”等关键词下的刷屏式的报道&#xff0c;Vidu 一下成为国产视频模型的一剂强心针。 尽管目前 Vidu 支持的视频长度是 16 …

二叉树理论和题目

二叉树的种类 在我们解题过程中二叉树有两种主要的形&#xff1a;满二叉树和完全二叉树。 满二叉树 满二叉树&#xff1a;如果一棵二叉树只有度为0的结点和度为 2 的结点&#xff0c;并且度为 0 的结点在同一层上&#xff0c;则这棵二叉树为满二叉树。 这棵二叉树为满二叉树…

vscode的终端区乱码怎么办呢?

vscode的终端区乱码怎么办呢? 错误效果解决办法一解决办法二(极力推荐方法二)最终效果参考文献 错误效果 解决办法一 解释:你之所以使用了utf8还乱码,是因为你的电脑目前根本无法兼容utf8,只兼容gbk 怎么让你的电脑兼容utf8,我写在方法二 在设置中,输入encoding 解决办法二(极…

水稻病害检测(YOLO数据集,多分类,稻瘟病、纹枯病、褐斑病、枯心病、霜霉病、水稻细菌性条纹斑病、稻苞虫)

是自己利用LabelImg工具进行手工标注&#xff0c;数据集制作不易&#xff0c;请尊重版权&#xff08;稻瘟病、纹枯病、褐斑病、枯心病、霜霉病、水稻细菌性条纹斑病、稻苞虫&#xff09; 如果需要yolv8检测模型和数据集放在一起的压缩包&#xff0c;可以关注&#xff1a;最新最…

求解约瑟夫问题

思路&#xff1a; 我们要创建两个指针 有一个指针pcur指向头结点&#xff0c;该pcur作为报数的指针&#xff0c;还有一个指针ptail指向尾结点&#xff0c;作为记录pcur的地址 每报数为m时&#xff0c;pcur指向下一个元素的地址&#xff0c;ptail销毁报数为m的地址&#xff0…

分光光度法基本原理与应用

本文介绍分光光度法基本原理与应用。 分光光度法是分光光度计采用的方法&#xff0c;在医疗检测仪器&#xff0c;实验室测量仪器中经常使用。本文简要分析其原理&#xff0c;并给出实际工作过程中如何应用及应用过程中可能的误差来源。 1.基本概念 设一平行单色光垂直照射某…

网络安全工程师必备的6个渗透测试工具

渗透测试是模拟黑客攻击&#xff0c;评估系统安全性的重要方法。 网络安全工程师需要掌握各种渗透测试工具&#xff0c;才能有效地发现和修复漏洞。 1. Nmap 功能: 强大的网络扫描器&#xff0c;可以扫描网络拓扑、识别主机和服务、发现开放端口和漏洞。 用途: 信息收集、漏洞…

一加Ace3/12/Ace2pro手机ColorOS14刷KernelSU内核ROOT-解决无限重启变砖

一加Ace3/一加12/一加11等手机升级了安卓14底层&#xff0c;并且ColorOS版本也更新到了14版本界面和功能都比之前的系统表现更加优秀&#xff0c;但刷机方面&#xff0c;相对之前存在一些差异&#xff0c;特别是KernelSU内核级别root权限&#xff0c;不再支持一键刷入KernelSU通…

MySQL的事务,函数和索引

事务 数据库的事务是一种机制&#xff0c;一种操作序列&#xff0c;包含了一组数据库的操作命令 简单了解&#xff1a;如果一个包含多个步骤的业务操作&#xff0c;被业务管理&#xff0c;要么这些操作同时操作成功&#xff0c;要么同时操作失败 事务是一个不可分割的工作逻…

HTTP网络协议,接口请求的内容类型 content-type(2024-04-27)

1、简介 Content-Type&#xff08;内容类型&#xff09;&#xff0c;一般是指网页中存在的 Content-Type&#xff0c;用于定义网络文件的类型和网页的编码&#xff0c;决定浏览器将以什么形式、什么编码读取这个文件&#xff0c;这就是经常看到一些 PHP 网页点击的结果却是下载…

【Kylin】V10系统在VMware中分辨率太小,无法通过GUI修改分辨率的解决方法

【Kylin】V10系统在VMware中分辨率太小&#xff0c;无法通过GUI修改分辨率的解决方法 解决办法1.打开终端方法1&#xff1a;方法2 2.输入 xrandr 命令&#xff0c;查询分辨率支持的列表3.选择适合的分辨率 。 例如&#xff1a;xrandr -s 1440x900_60 问题如下图&#xff1a; 保…

C++感受10-Hello Object 生死版•下

搞懂以下三个重要知识点&#xff1a; 对象生命周期对象内存模型对象的可见性 ff12-HelloObject-生死版-下 1. 生命周期 只要是数据&#xff0c;就需要占用内存空间。程序将内存分成多个区&#xff0c;其中最重要的是栈区和堆区。放在栈区的数据&#xff0c;称为栈数据或栈对象&…

uniapp分包,以及通过uni-simple-router进行分包

先说一下uniapp的直接分包方式&#xff0c;很简单&#xff1a; 配置分包信息 打开manifest.json源码视图&#xff0c;添加 “optimization”:{“subPackages”:true} 开启分包优化 我们在根目录下创建一个pagesA文件夹&#xff0c;用来放置需要分包的页面 然后配置路由 运行到…