秒懂Linux之共享内存

news2024/9/21 7:47:12
fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

共享内存概念

模拟实现共享内存

创建key阶段

​编辑创建共享内存阶段

删除共享内存阶段 

查看共享内存属性阶段

挂接共享内存到进程阶段

取消共享内存与进程挂接阶段

进程通信阶段

添加管道改进版

共享内存函数

shmget函数

shmat函数

shmdt函数

shmctl函数


共享内存概念

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

之前所学的管道通信本质上是通过内核的文件作为媒介进行通信的,而共享内存则是脱离创建管道的范畴转而到物理内存中让两进程通信~

然而共享内存可以存在很多个,因为不仅仅只有进程A,B需要利用共享内存通信,其他进程之间也需要,所以OS必须管理所有的共享内存~如何管理呢?——先描述,再组织~

那么核心问题来了,如何让不同进程之间看到同一份共享内存呢?前的管道通信是通过创建管道这种特殊文件让不同进程在内核中通过打开同一个文件来建立通信~而在共享内存中进程之间应该如何做到呢?总不能共享内存被创建起那进程A,B就自然而然可以通信了吧?

如果key由OS生成,它哪里知道我们的心思,是无法保证让指定进程看到同一份共享内存的这样倒不如我们自己给一个参数(ftok)来让进程A,B作为同一个参数形成key,这样就可以看到同一份内存了~

模拟实现共享内存

创建key阶段

//设置好生成key的两个参数
const char* pathname = "/home/LJZ";
const int proj_id = 0x66;

//转16进制
std::string ToHex(key_t k)
{
    char buffer[1024];
    //将k以16进制的格式写入buffer
    snprintf(buffer,sizeof(buffer),"%x",k);
    return buffer;
}


//获取key函数
key_t GetShmKey()
{
    //生成key
    key_t k = ftok(pathname,proj_id);
    if (k < 0)
    {
        std::cerr << "ftok error, errno : " << errno << ", error string: " << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}
#include "Comm.hpp"

int main()
{
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;
    
    return 0;
}

 成功让客户端与服务端都拿到了我们自定义生成的key~



创建共享内存阶段

//创建共享内存函数
int CreatShm(key_t key,int size,int flag)
{
    //shmget创建共享内存的返回值类似我们创建文件的文件描述符fd
    int shmid = shmget(key,size,flag);
    if (shmid < 0)
    {
        std::cerr << "shmget error, errno : " << errno << ", error string: " << strerror(errno) << std::endl;
        exit(2);
    }
    return shmid;
}

//为服务端使用,由服务端来创建共享内存
int SSCreateShm(key_t key, int size)
{
    // IPC_CREAT: 不存在就创建,存在就获取
    // IPC_EXCL: 没有意义
    // IPC_CREAT | IPC_EXCL: 不存在就创建,存在就出错返回
    return CreatShm(key, size, IPC_CREAT | IPC_EXCL | 0666);
}

//为客户端使用,在共享内存存在的情况下获取它
int SCGetShm(key_t key, int size)
{
    return CreatShm(key, size, IPC_CREAT);
}
int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;
    //获取共享内存
    int shmid = SCGetShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;

    return 0;
}
int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;
    //创建共享内存
    int shmid = SSCreateShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;

    return 0;
}

以上说明在进程结束时如果我们没有主动释放掉共享内存它会一直存在~

ps:ipcrm -m shmid :手动删除共享内存 

删除共享内存阶段 


//删除共享内存函数
void DeleteShm(int shmid)
{
    //删除共享内存
    int n = shmctl(shmid,IPC_RMID,nullptr);
     if (n < 0)
    {
        std::cerr << "shmctl error" << std::endl;
    }
    else
    {
        std::cout << "shmctl delete shm success, shmid: " << shmid << std::endl;
    }
}

int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;
    //创建共享内存
    int shmid = SSCreateShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;

    sleep(10);
    //10s后删除共享内存
    DeleteShm(shmid);
    
    return 0;
}

查看共享内存属性阶段

//查看共享内存属性函数
void ShmDebug(int shmid)
{
    //shmid_ds里面存储着其信息
    struct shmid_ds shmds;
    //将与标识符为 shmid 的共享内存段相关联的内核数据结构中的信息复制到由 buf 所指向的 shmid_ds 结构中
    int n = shmctl(shmid, IPC_STAT, &shmds);
    if (n < 0)
    {
        std::cerr << "shmctl error" << std::endl;
        return;
    }
    std::cout << "shmds.shm_segsz: " << shmds.shm_segsz << std::endl;
    std::cout << "shmds.shm_nattch:" << shmds.shm_nattch << std::endl;
    std::cout << "shmds.shm_ctime:" << shmds.shm_ctime << std::endl;
    std::cout << "shmds.shm_perm.__key:" << ToHex(shmds.shm_perm.__key) << std::endl;
}

int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;
    //创建共享内存
    int shmid = SSCreateShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;

    //查看共享内存信息
    ShmDebug(shmid);

    sleep(10);
    //10s后删除共享内存
    DeleteShm(shmid);
    
    return 0;
}

以上只是为了演示shmctl中模式的多样化选择~

挂接共享内存到进程阶段


//挂接共享内心到进程函数
void *ShmAttach(int shmid)
{
    //挂接进程,成功则返回已连接的共享内存段的地址。
    void *addr = shmat(shmid, nullptr, 0);
     if ((long long int)addr == -1)
    {
        std::cerr << "shmat error" << std::endl;
        return nullptr;
    }
 
    return addr;
}

int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;
    
    //获取共享内存
    int shmid = SCGetShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;
    sleep(2);

    //挂接共享内存到客户端
    char * addr = (char*)ShmAttach(shmid);
    std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(5);


    return 0;
}
int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;
    //创建共享内存
    int shmid = SSCreateShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;

    //挂接共享内存到服务端
    char * addr = (char*)ShmAttach(shmid);
    std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(10);
    //查看共享内存信息
    //ShmDebug(shmid);

    sleep(100);
    //10s后删除共享内存
    DeleteShm(shmid);
    
    return 0;
}

有时候我们不想让某一个进程挂接共享内存了,总不能直接把共享内存删掉吧?可以取消它与共享内存的挂接~

取消共享内存与进程挂接阶段

//取消共享内存与进程挂接函数
void ShmDetach(void *addr)
{
    //通过进程的虚拟地址取消挂接
    int n = shmdt(addr);
    if (n < 0)
    {
        std::cerr << "shmdt error" << std::endl;
    }
}
int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;

    //获取共享内存
    int shmid = SCGetShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;
    sleep(2);

    //挂接共享内存到客户端
    char * addr = (char*)ShmAttach(shmid);
    std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(5);

    //客户端取消挂接
    ShmDetach(addr);
    std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;

    return 0;
}

int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;
    //创建共享内存
    int shmid = SSCreateShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;

    //挂接共享内存到服务端
    char * addr = (char*)ShmAttach(shmid);
    std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(10);
    //查看共享内存信息
    //ShmDebug(shmid);

    //服务端取消挂接
    ShmDetach(addr);
    std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;


    sleep(20);
    //10s后删除共享内存
    DeleteShm(shmid);
    
    return 0;
}

所有工作准备完毕,进入通信阶段~


进程通信阶段

int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;
    //创建共享内存
    int shmid = SSCreateShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;

    //挂接共享内存到服务端
    char * addr = (char*)ShmAttach(shmid);
    std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(10);
    //查看共享内存信息
    //ShmDebug(shmid);

    
    std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    //通信阶段,服务端读取数据
    //直接打印在共享内存中的数据
    for(;;)
    {
        cout << "shm content: " << addr << std::endl;
        sleep(1);
    }
    sleep(100);
    //服务端取消挂接
    ShmDetach(addr);
    sleep(20);
    //10s后删除共享内存
    DeleteShm(shmid);
    
    return 0;
}

int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;

    //获取共享内存
    int shmid = SCGetShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;
    sleep(2);

    //挂接共享内存到客户端
    char * addr = (char*)ShmAttach(shmid);
    std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(5);

    //通信阶段,客户端写入数据
    //客户端直接在共享内存中写数据
    memset(addr,0,defaultsize);
    for (char c = 'A'; c <= 'Z'; c++) 
    {
        addr[c - 'A'] = c;
        sleep(1);
    }
    sleep(100);
    //客户端取消挂接
    ShmDetach(addr);
    std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(5);
    return 0;
}

不过这种通信有个无法避免的缺陷:在默认情况中作为shm读取方是不会去阻塞等待写入方的,就一直读取不管有没有写入优点是共享内存这种方式是进程通信中速度最快的,因为写入数据的同时另外一方直接在内存中就可以读取,不需要管道传递的媒介但缺点也很明显,无法提供进程间协同的任何机制,如果我们想要发送一整段字符串时会被这种方式切割为一个个字符读入~

为了能控制想要发送与读取的大小,我们利用管道来实现同步的机制~

添加管道改进版


#define Mode 0666
#define Path "./fifo"

//命名管道
class Fifo
{
public:
    Fifo(const string &path = Path) : _path(path)
    {
        umask(0);
        int n = mkfifo(_path.c_str(), Mode);
        if (n == 0)
        {
            cout << "mkfifo success" << endl;
        }
        else
        {
            cerr << "mkfifo failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
        }
    }
    ~Fifo()
    {
        int n = unlink(_path.c_str());
        if (n == 0)
        {
            cout << "remove fifo file " << _path << " success" << endl;
        }
        else
        {
            cerr << "remove failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
        }
    }

private:
    string _path; // 文件路径+文件名
};

//利用管道进行同步数据
class Sync
{
public:
    Sync() : rfd(-1), wfd(-1)
    {
    }
    //以读方式打开管道
    void OpenRead()
    {
        rfd = open(Path, O_RDONLY);
        if (rfd < 0)
            exit(1);
    }
    //以写方式打开管道
    void OpenWrite()
    {
        wfd = open(Path, O_WRONLY);
        if (wfd < 0)
            exit(1);
    }
    //让服务端等待管道中写端的写入,到达一定量后再一次性读取
    bool Wait()
    {
        bool ret = true;
        uint32_t c = 0;
        ssize_t n = read(rfd, &c, sizeof(uint32_t));
        //读取的数据大小为uint32_t时提示准备唤醒服务端读取数据代替之前时时刻刻读
        if (n == sizeof(uint32_t))
        {
            std::cout << "server wakeup, begin read shm..." << std::endl;
        }
        //代表管道写端关闭,读端读取数据结束,返回0
        else if (n == 0)
        {
            ret = false;
        }
        else
        {
            return false;
        }
        return ret;
    }
    //往管道写入数据,量够的时候作提示唤醒服务端
    void Wakeup()
    {
        uint32_t c = 0;
        //往管道写入数据
        ssize_t n = write(wfd, &c, sizeof(c));
        //写够数据量的时候,提示唤醒服务端可以读取了
        assert(n == sizeof(uint32_t));

        std::cout << "wakeup server..." << std::endl;
    }
    ~Sync() {}

private:
    int rfd;
    int wfd;
};
#include "Comm.hpp"
#include <unistd.h>
#include "Fifo.hpp"
int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;

    //获取共享内存
    int shmid = SCGetShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;
    sleep(2);

    //挂接共享内存到客户端
    char * addr = (char*)ShmAttach(shmid);
    std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(5);

    //通信阶段,客户端写入数据
    //客户端直接在共享内存中写数据
    memset(addr,0,defaultsize);
    Sync syn;
    //写方式打开管道
    syn.OpenWrite();
    for (char c = 'A'; c <= 'Z'; c++) 
    {
        addr[c - 'A'] = c;
        sleep(1);
        //往管道写入数据,写够时发出提示:可唤醒服务端读取数据
        syn.Wakeup();
    }
    sleep(100);
    //客户端取消挂接
    ShmDetach(addr);
    std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(5);
    return 0;
}


int main()
{
    //获取key
    key_t key = GetShmKey();
    std::cout << "key: " << ToHex(key) << std::endl;
    //创建共享内存
    int shmid = SSCreateShm(key,defaultsize);
    std::cout << "shmid: " << shmid << std::endl;

    //挂接共享内存到服务端
    char * addr = (char*)ShmAttach(shmid);
    std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(10);
    //查看共享内存信息
    //ShmDebug(shmid);

    // 0. 先引入管道
    Fifo fifo;
    Sync syn;
    //读方式打开管道
    syn.OpenRead();
    
    std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    //通信阶段,服务端读取数据
    //直接打印在共享内存中的数据
    for(;;)
    {
        //读取数据量不够的时候,退出,不让打印(读取)出内容
        //读取数据量够的时候,打印堆积的内容
        if(!syn.Wait()) break;
        cout << "shm content: " << addr << std::endl;
        sleep(1);
    }
    sleep(100);
    //服务端取消挂接
    ShmDetach(addr);
    sleep(20);
    //10s后删除共享内存
    DeleteShm(shmid);
    
    return 0;
}

管道意义就在于不要让服务端读那么快,而是在管道内积累一定数据量后再一次性读取数据,实现同步的机制。

共享内存函数

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: 将要采取的动作(有三个可取值)
buf: 指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回 0 ;失败返回 -1

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

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

相关文章

第二十一节:学习Redis缓存数据库的Hash操作(自学Spring boot 3.x的第五天)

这节记录下Redis的Hash操作。主要是opsForHash方式和boundHashOps方式。 boundHashOps和opsForHash都是Spring Data Redis中用于操作Redis哈希数据结构的方法&#xff0c;但它们在使用方式和场景上存在一些区别。 boundHashOps 使用方式&#xff1a; boundHashOps方法通过Redi…

【第十三章:Sentosa_DSML社区版-机器学习聚类】

目录 【第十三章&#xff1a;Sentosa_DSML社区版-机器学习聚类】 13.1 KMeans聚类 13.2 二分KMeans聚类 13.3 高斯混合聚类 13.4 模糊C均值聚类 13.5 Canopy聚类 13.6 Canopy-KMeans聚类 13.7 文档主题生成模型聚类 13.8 谱聚类 【第十三章&#xff1a;Sentosa_DSML社…

C#基于SkiaSharp实现印章管理(8)

上一章虽然增加了按路径绘制文本&#xff0c;支持按矩形、圆形、椭圆等路径&#xff0c;但测试时发现通过调整尺寸、偏移量等方式不是很好控制文本的位置。相对而言&#xff0c;使用弧线路径&#xff0c;通过弧线起始角度及弧线角度控制文本位置更简单。同时基于路径绘制文本时…

Chainlit集成LlamaIndex实现知识库高级检索(简单融合寻回器)

检索原理 ** 简单融合寻回器 ** 简单融合寻回原理&#xff0c;是利用多个检索器&#xff0c;融合查询最终的结果返回给LLM。此检索器还将通过生成与原始问题相关的问题&#xff0c;用相关问题再次检索多个检索器的数据&#xff0c;把原始问题和相关问题经过多个检索器检索结果…

Relations Prediction for Knowledge Graph Completion using Large Language Models

文章目录 题目摘要简介相关工作方法论实验结论局限性未来工作 题目 使用大型语言模型进行知识图谱补全的关系预测 论文地址&#xff1a;https://arxiv.org/pdf/2405.02738 项目地址&#xff1a; https://github.com/yao8839836/kg-llm 摘要 知识图谱已被广泛用于以结构化格式表…

高级java每日一道面试题-2024年9月20日-分布式篇-什么是CAP理论?

如果有遗漏,评论区告诉我进行补充 面试官: 什么是CAP理论&#xff1f; 我回答: 在Java高级面试中&#xff0c;CAP理论是一个经常被提及的重要概念&#xff0c;它对于理解分布式系统的设计和优化至关重要。CAP理论是分布式系统理论中的一个重要概念&#xff0c;它描述了一个分…

【数学分析笔记】第3章第2节 连续函数(4)

3. 函数极限与连续函数 3.2 连续函数 3.2.9 反函数的连续性定理 【定理3.2.2】【反函数连续性定理】设 y f ( x ) yf(x) yf(x)在闭区间 [ a , b ] [a,b] [a,b]上连续且严格单调增加&#xff0c;设 f ( a ) α , f ( b ) β f(a)\alpha,f(b)\beta f(a)α,f(b)β&#xff0…

仓颉编程入门

#体验华为仓颉编程语言# 仓颉发布的第一时间&#xff0c;就申请了测试。昨天发现申请通过 &#xff0c;果断下载SDK体验一下。 废话不多说&#xff0c;从下载完开始&#xff0c;下面这个图&#xff0c;就是下载的文件&#xff1a; 看文件夹样子跟c/c套路差不多。bin目录是cjc…

linux安装nginx+前端部署vue项目(实际测试react项目也可以)

&#x1f9f8;本篇博客作者测试上线过不下5个项目&#xff0c;包括单纯的静态资源&#xff0c;vue项目和react项目&#xff0c;包好用&#xff0c;请放心使用 &#x1f4dc;作者首页&#xff1a;dream_ready-CSDN博客 &#x1f4dc;有任何问题都可以评论留言&#xff0c;作者将…

什么是大模型的泛化能力?

大模型的泛化能力指的是模型在未见过的数据上表现的能力&#xff0c;即模型不仅能在训练数据上表现良好&#xff0c;也能在新的、未知的数据集上保持良好的性能。这种能力是衡量机器学习模型优劣的重要指标之一。 泛化能力的好处包括但不限于&#xff1a; 提高模型的适应性&a…

基于uniapp的民宿酒店预订系统(后台+小程序)

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

F28335中断系统

1 中断介绍 1.1 中断概念 1.2 TMS320F28335 中断概述

CUDA并行架构

一、CUDA简介 CUDA(Compute Unified Device Architecture)是一种由NVIDIA推出的通用并行计算架构&#xff0c;该架构使GPU(Graphics Processing Unit)能够对复杂的计算问题做性能速度优化。 二、串并行模式 高性能计算的关键是利用多核处理器进行并行计算。 串行模式&#…

使用LangGPT提示词让大模型比较浮点数

使用LangGPT提示词让大模型比较浮点数 背景介绍环境准备创建虚拟环境安装一些必要的库安装其他依赖部署大模型启动图形交互服务设置提示词与测试 LangGPT结构化提示词 背景介绍 LLM在对比浮点数字时表现不佳&#xff0c;经验证&#xff0c;internlm2-chat-1.8b (internlm2-cha…

Excel-时间取整,工作有效时长计算

在计算考勤时&#xff0c;打卡时间不是整点&#xff0c;上班时间是遵循整点开始计算的&#xff0c;员工提前打卡&#xff0c;所以要用到时间向上取整。 上班取整&#xff1a; 使用CEILING函数可实现该需求&#xff0c;参考以下公式&#xff0c;第一个参数为上班打卡时间&#…

MySQL篇(窗口函数/公用表达式(CTE))(持续更新迭代)

目录 讲解一&#xff1a;窗口函数 一、简介 二、常见操作 1. sumgroup by常规的聚合函数操作 2. sum窗口函数的聚合操作 三、基本语法 1. Function(arg1,..., argn) 1.1. 聚合函数 sum函数&#xff1a;求和 min函数 &#xff1a;最小值 1.2. 排序函数 1.3. 跨行函数…

一文读懂SpringCLoud

一、前言 只有光头才能变强 认识我的朋友可能都知道我这阵子去实习啦&#xff0c;去的公司说是用SpringCloud(但我觉得使用的力度并不大啊~~)… 所以&#xff0c;这篇主要来讲讲SpringCloud的一些基础的知识。(我就是现学现卖了&#xff0c;主要当做我学习SpringCloud的笔记吧&…

英集芯IP5902:集成电压可调异步升压转换充电管理功能的8位MCU芯片

英集芯IP5902是一款集成了9V异步升压转换、锂电池充电管理及负端NMOS管的8-bit MCU芯片&#xff0c;外壳采用了SOP16封装形式&#xff0c;高集成度和丰富的功能使其在应用时只需很少的外围器件&#xff0c;就能有效减小整体方案的尺寸&#xff0c;降低BOM成本&#xff0c;为小型…

Vue使用axios实现Ajax请求

1、什么是 axios 在实际开发过程中&#xff0c;浏览器通常需要和服务器端进行数据交互。而 Vue.js 并未提供与服务器端通信的接口。从 Vue.js 2.0 版本之后&#xff0c;官方推荐使用 axios 来实现 Ajax 请求。axios 是一个基于 promise 的 HTTP 客户端。 关于 promise 的介绍…

C#开源的一个能利用Windows通知栏背单词的软件

前言 今天给大家推荐一个C#开源且免费的能利用Windows通知栏背单词的软件&#xff0c;可以让你在上班、上课等恶劣环境下安全隐蔽地背单词&#xff08;利用摸鱼时间背单词的软件&#xff09;&#xff1a;ToastFish。 操作系统要求 目前该软件只支持Windows10及以上系统&…