【Linux】进程间通信(匿名管道和命名管道通信、共享内存通信)
文章目录
- 1、进程间通信
- 1.1 进程的通信
- 1.2 如何让进程间通信?
- 1.3 进程间通信的本质
- 2、管道通信
- 2.1 匿名管道
- 2.2 匿名管道通信
- 2.3 命名管道
- 2.4 命名管道的通信
- 3、SystemV中的共享内存通信
- 3.1 共享内存
- 3.2 共享内存的通信
- 3.3 共享内存的缺点以及数据保护
- 3.4 共享内存的优点
- 3.5 信号量以及与共享内存有关的概念
1、进程间通信
1.1 进程的通信
在一些应用场景下,一定要求着进程之间进行通信,而通信有以下这些:
数据传输:进程间将数据传递给彼此。
资源共享:多个进程共享同一份资源。
通知事件:一个进程向另一个进程发送信息,通知它发生了某种事件。(比如父进程向子进程传递信号,让子进程退出。)
进程控制:一个进程控制另一个进程。(比如debug)
比如在linux中通过命令,ps ajx | grep hello,两个命令分别对应着两个进程,通过管道进行进程通信。(也就是说,为了完成某些业务,我们是需要多进程进行协同的)
1.2 如何让进程间通信?
Linux的主流通信标准有以下:
- POSIX (让通信过程可以跨主机)
- System V (聚焦在本地通信这种方式,这种标准有着三种通信方式:共享内存、消息队列、信号量(这里只谈共享内存,因为SystemV标准不常用)。)
第二套通信方案
管道是最基本的通信方案,管道基于文件系统。
管道有着匿名管道和命名管道。
1.3 进程间通信的本质
首先,进程是具有独立性的,所以如果要让进程进行通信肯定是不能进程与进程直接联系的。
那么就需要以下:
1、OS需要直接或间接的给通信双方的进程提供"共享空间"。
2、要让通信的进程都看到同一份共享空间。
(而不同的通信种类,本质就是这边共享空间的不同,区别在OS哪个模块提供的。比如通过文件系统提供的资源就是管道。)
综上,我们需要做的就是:
1、需要让进程看到同一份资源。(这个是主要的)
2、通信
2、管道通信
2.1 匿名管道
理解管道通信:
在讲文件的时候说到过,一个进程的PCB中有一个指针,指向文件描述符表,被打开的文件被其中文件描述符下标指向。
如果有一个父进程打开了一个文件,那么文件描述符表中对应就会指向这个文件对应的结构体对象,通过这个文件结构体中有着文件的操作方法以及一个内核缓冲区。
父进程创建子进程后,子进程拷贝父进程,对应文件描述符表中对应也会指向这个文件对应结构体对象。
综上,两个进程指向同一文件资源,这个文件就是一个"共享空间",也是一个管道。
另一个问题,普通文件传输数据是需要经过磁盘的,如果进程间通过文件传输数据经过磁盘,那么就是内存与外设的交互,效率很低。那么OS能不能只创建一个文件结构体对象,只在内存中交互呢?
答案是肯定的,这个文件不会真的存在,OS会申请一个文件结构体对象。
同时,这个文件是一个内存级文件没有名字也没有对应inode,所以称之为匿名管道。
管道只能单向通信
首先创建管道将读写端返回给进程。
为了让子进程也看到读写端,父进程fork创建子进程。
因为管道需要单向通信,所以对应读端要关闭写端,对应写端要关闭读端。
综上,匿名管道就是一个父进程通过读和写的方式打开一个内存级文件后,通过创建子进程,关闭各自读写端,所构成的通信信道,这个信道基于文件,是一个内存级文件,所以称为匿名管道。
2.2 匿名管道通信
pipe接口:
pipe系统接口,可以创建一个单向管道用于进程间通信,pipefd[2]参数是一个返回型参数,返回两个文件描述符,一个读端fd[0]和一个写端fd[1],指向管道文件的两边。
知道了如何创建管道后,下面需要让两个进程都看到这个"共享空间" 因为管道返回的是文件描述符,所以通过文件描述符就可以访问管道。
综上,思路就很简单:
1. 创建管道,接收管道文件描述符。
2. 父进程创建子进程,进程会获取和父进程一样的文件描述符。
3. 可以让父进程读,子进程写,然后父进程需要关闭写端,子进程关闭读端。
4. 通过文件操作进行通信。
测试代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;
int main()
{//第一步:创建管道文件,打开读写端int fds[2];int n = pipe(fds);assert(n == 0);//第二步:forkpid_t id = fork();assert(id >= 0);if(id == 0){//子进程写入close(fds[0]);//子进程的通信代码int cnt = 0;while(true){char buffer[1024];snprintf(buffer, sizeof buffer, "child->parent say: %s[%d][%d]","我是子进程,我正在发消息", cnt++, getpid());//一直往管道输入端写,写满也会阻塞write(fds[1], buffer, strlen(buffer));cout << "count: " << cnt << endl;sleep(2); //每隔2s, 写一次 }close(fds[1]); //写完 子进程关闭写端cout << "子进程关闭自己写端" << endl;sleep(5);exit(0);}//父进程读取close(fds[1]);//父进程的通信代码while(true){char buffer[1024];//如果管道中没有了数据,读端在读,默认会直接阻塞当前正在读取的进程!ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);if(s > 0){buffer[s] = 0;cout << "Get Message# " << buffer << "| my pid:"<< getpid() << endl;//break;}else if(s == 0){//读到文件末尾cout << "read: " << s << endl;break;}//父进程没有sleep}close(fds[0]);int status = 0;n = waitpid(id, &status, 0);cout << "waitpid:" << n << " sig:" << (status & 0x7f) << endl;assert(n == id);return 0;
}
匿名管道读写特性:
之前,主要做的是让进程间看到同一份资源,而通信的过程是可以任意想象的,但是大概就以下几个情况。
1、写的快,读的慢。(那么每次读取的时候,就会从缓冲区读一大堆)
2、写的慢,读的快。
3、写后关闭写端,读端继续。(当读完后,子进程退出,父进程需要回收子进程)
4、一直写,读端关闭。(这种情况的出现,让数据没有意义)
前三个很好理解,如果第四种情况出现,写端一直写,读端却不读,那么写的数据就没有任何意义,造成了空间浪费。
当写端还在,读端关闭这个条件发生时,操作系统为了避免空间浪费,就会给父进程发送SIGPIPE(13)信号,关闭父进程。
管道特点:
- 管道的生命周期随进程。
- 管道可以用来进行具有"血缘关系"的进程间进行通信,常用于父子间通信。
- 管道是面向字节流的。
- 管道只允许单向通信 半双工的。(任何时候都是一方向另一方发送数据)
- 互斥和同步机制。(共享资源保护机制)(这个可以和后面共享内存进行比较)
2.3 命名管道
前面的匿名管道,是有"血缘关系"的进程间进行通信的。
如果想让毫不相关的两个进程通过管道通信呢?命名管道就能解决这个问题。
命名管道相较于匿名管道就是在磁盘有一个特殊的管道文件,这个文件在打开后也拥有着自己的struct file。
两个不相关的进程是如何看到同一份资源的呢?
可以让不同进程,通过文件名(路径+文件名)打开。(匿名管道的结构体对象中的地址没有名称,命名管道的结构体对象中的地址拥有名称)
值得注意的是,进程传递到内核缓冲区的数据不会刷新到磁盘上。
2.4 命名管道的通信
函数调用mkfifo:
函数调用mkfifo用于创建命名管道,也是一个特殊的文件。
pathname: 表示创建文件的路径,如果只是一个文件名就和进程在同一路径。
mode: 表示创建的文件名拥有的初始权限。(前提设置umask(0),不然结果就是mode & ~umask)
创建成功返回0,失败返回-1。
函数调用unlink:
unlink在此用于删除管道文件,只需传管道路径,成功返回0,错误返回-1。
对应通信也很简单。
1、创建管道文件,进程1打开文件,向文件里写入。
2、进程2打开文件向文件里读取,读完后删除管道文件。
//comm.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <fcntl.h>#define NAMED_PIPE "mypipe"bool createFifo(const std::string& path)
{umask(0);int n = mkfifo(path.c_str(), 0666);if(n == 0){return true;}else{std::cout << "errno: " << errno << "err stirng:" << strerror(errno) << std::endl;return false;}
}void removeFifo(const std::string& path)
{int n = unlink(path.c_str());assert(n == 0);(void)n;
}#include "comm.hpp"int main()
{createFifo(NAMED_PIPE);int wfd = open(NAMED_PIPE, O_WRONLY, 0666);int cnt = 10;while(cnt--){char buffer[1024];std::cout << "client begin:" << std::endl;fgets(buffer, sizeof buffer, stdin);std::cout << "client end:" << std::endl;ssize_t w = write(wfd, buffer, strlen(buffer) - 1);std::cout << std::endl;}close(wfd);return 0;
}#include "comm.hpp"int main()
{//createFifo(NAMED_PIPE);int rfd = open(NAMED_PIPE, O_RDONLY, 0666);while(true){std::cout << "server begin:" << std::endl;char buffer[1024];ssize_t r = read(rfd, buffer, sizeof(buffer));if(r > 0){buffer[r] = 0;std::cout << buffer << std::endl;std::cout << "server end:" << std::endl;std::cout << std::endl;}else{std::cout << "read:" << r << std::endl;break;}}close(rfd);removeFifo(NAMED_PIPE);return 0;
}
3、SystemV中的共享内存通信
3.1 共享内存
共享内存的理解很简单。
用户通过接口,让OS在物理空间申请一块内存。
不同的进程将进程地址空间通过页表和这块内存进行映射。
这就做到了共享同一份空间。
下面通过接口,看进程是怎么和共享内存联系起来的。
认识接口
shmget:
shmget让OS在物理空间上申请一个共享内存
key是一个系统层面的标识符,用于标识共享内存唯一性。(这key是由一个函数生成)
size 表示要创建多大的共享内存。
shmflg 是一个二进制标识符,一般为IPC_CREAT 表示共享内存没有时创建,有时不创建;为IPC_CREAT | IPC_EXCL 表示没有时创建,有时会报错。
成功返回一个用户层面的标识符,用于标识共享内存唯一性,错误返回-1。
也正是因为这个返回值和文件描述符没有关联,使得SystemV标准作为自立的一套标准
ftok:
前面shmget所说的参数key,就是ftok的返回值,用于标识共享内存唯一性。
pathname和proj_id,可以是符合类型的任意值,ftok会根据自己算法将这两个参数转换成返回值key。
这也说明,如果多个进程的pathname和proj_id都用一样的话,就能通过返回值key访问同一个共享内存。
shmctl:
有了创建共享内存的函数就有可以释放共享内存的函数。
值得注意的是shmclt不仅可以释放共享内存,也有着访问共享内存的属性作用,这里只说怎么释放内存。
shmid :这个值是shmget的返回值,指的是用户层面上标识共享内存唯一性的。
cmd :是个二进制标识符,IPC_RMID 代表释放共享内存。
buf 是用于访问共享内存属性的参数,这里可以置空。
成功返回0,失败返回-1.
有了shmget和ftok,就可以创建共享内存了,接下来就需要考虑如何将进程和共享内存关联起来。
不过在此之前先再认识下共享内存:
首先前面说过,内存中一定会在一定时刻存在多个共享内存,多个共享内存就一定需要被操作系统管理,管理就一定需要结构化。
共享内存 = 物理空间块 + 自身属性。
而管理共享内存的结构体struct shm其中就保存了作为系统层面的key,用于标识唯一性。
3.2 共享内存的通信
共享内存的系统命令:
通过ipcs -m 可以查看当前开辟的共享内存
如果要通过命令释放共享内存,可以通过ipcrm -m 对应shmid
通过while :; do ipcs -m; sleep 2; done 也可以一直看到共享内存信息。
共享内存的通信:
首先进程和共享内存的联系,也是通过函数进行联系的。
shmat(attach 附加) 和 shmdt(detach 分离)
shmat 将内存段连接到进程地址空间
shmid 是shmget返回的用户层面上的唯一标识符
一般都是shmat(id, nullptr, 0),让它自动连接将当前进程联系到共享内存段。
返回值返回一个指针,指向共享内存的第一个内存段。如果失败返回(void*)-1
shmdt 取消进程与共享内存的关联。
只需要传一个shmat返回的地址就行,就能取消联系。
成功返回0,失败返回-1。
共享内存进行通信
//comm.hpp
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <unistd.h>
#include <cassert>#define SHMSIZE 4026
#define PATHNAME "."
#define PROJ_ID 0x66key_t getKey(const char* path, int proj_id)
{key_t k = ftok(path, proj_id);if(k < 0){std::cout << "ftok:" << errno << ":" << strerror(errno) << std::endl;exit(-1);}return k;
}int getShmHelp(key_t k, int shmflg)
{int id = shmget(k, SHMSIZE, shmflg);if(id < 0){std::cout << "shmget: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}int createShm(key_t k)
{umask(0);return getShmHelp(k, IPC_CREAT | IPC_EXCL | 0600);
}int getShm(key_t k)
{return getShmHelp(k, IPC_CREAT);
}void* attachShm(int shmId)
{void* mem = shmat(shmId, nullptr, 0);if((long long)mem == -1L) //linux 64位{std::cout << "shmat: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}void detachShm(void* start)
{if(shmdt(start) == -1){std::cout << "shmdt: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}void delShm(int shmId)
{if(shmctl(shmId, IPC_RMID, nullptr) == -1){std::cout << "shmctl: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}///
//client
#include "comm.hpp"int main()
{//先获取key,用于唯一标识共享内存key_t key = getKey(PATHNAME, PROJ_ID);printf("key:0x%x\n", key);//用户创建共享内存int shmId = createShm(key);printf("shmId:%d\n", shmId);//进程与内存关联char* start = (char*)attachShm(shmId);printf("attach success, address start: %p\n", start);//进程通信const char* message = "hello server, 我是另一个进程,正在和你通信";pid_t id = getpid();int cnt = 1;int circnt = 5;while(circnt--){sleep(5);snprintf(start, SHMSIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);}//sleep(10);//删除前最好取消关联detachShm(start);//删除共享内存//delShm(shmId);return 0;
}///
//server
#include "comm.hpp"int main()
{//先获取key,用于唯一标识共享内存key_t key = getKey(PATHNAME, PROJ_ID);printf("key:0x%x\n", key);//用户创建共享内存int shmId = getShm(key);printf("shmId:%d\n", shmId);//进程与内存关联char* start = (char*)attachShm(shmId);printf("attach success, address start: %p\n", start);//进程通信while(true){struct shmid_ds sd;shmctl(shmId, IPC_STAT, &sd);printf("client say : %s, cpid[%d], key[0x%x]\n", start, sd.shm_cpid, sd.shm_perm.__key); sleep(1);}//sleep(9);//删除前最好取消关联detachShm(start);//删除共享内存delShm(shmId);return 0;
}
输出结果:
3.3 共享内存的缺点以及数据保护
根据上一节的输出结果,进行分析
首先client进程每五秒写到共享内存一次,而server每隔一秒向共享内存中读取。
这也说明共享内存方式通信是有缺点的:不给我进行同步和互斥操作的,对数据没有进行保护。
也就是不像管道那样,一份数据写完,读端再读。
对共享内存进行保护,需要写完,通知读端,再读。
没通知server的时候,让server进行阻塞状态。
通过在外层再设计一层命名管道,写端写入一个字符,如果读端收到那么就从共享内存中读取,没有收到就进入阻塞状态(read读不到数据)
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <cassert>
#include <string>#define SHMSIZE 4026
#define PATHNAME "."
#define PROJ_ID 0x66
#define FIFOPATH "mypipe"void createFifo(const std::string& path)
{umask(0);int n = mkfifo(path.c_str(), 0666);if(n < 0){std::cout << "mkfifo:" << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}void removeFifo(const char* path)
{if(unlink(path) == -1){std::cout << "unlink:" << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}key_t getKey(const char* path, int proj_id)
{key_t k = ftok(path, proj_id);if(k < 0){std::cout << "ftok:" << errno << ":" << strerror(errno) << std::endl;exit(-1);}return k;
}int getShmHelp(key_t k, int shmflg)
{int id = shmget(k, SHMSIZE, shmflg);if(id < 0){std::cout << "shmget: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}int createShm(key_t k)
{umask(0);return getShmHelp(k, IPC_CREAT | IPC_EXCL | 0600);
}int getShm(key_t k)
{return getShmHelp(k, IPC_CREAT);
}void* attachShm(int shmId)
{void* mem = shmat(shmId, nullptr, 0);if((long long)mem == -1L) //linux 64位{std::cout << "shmat: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}void detachShm(void* start)
{if(shmdt(start) == -1){std::cout << "shmdt: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}void delShm(int shmId)
{if(shmctl(shmId, IPC_RMID, nullptr) == -1){std::cout << "shmctl: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}///
//client
#include "comm.hpp"int main()
{//建立命名管道createFifo(FIFOPATH);//先获取key,用于唯一标识共享内存key_t key = getKey(PATHNAME, PROJ_ID);printf("key:0x%x\n", key);//用户创建共享内存int shmId = createShm(key);printf("shmId:%d\n", shmId);//进程与内存关联char* start = (char*)attachShm(shmId);printf("attach success, address start: %p\n", start);//进程通信const char* message = "hello server, 我是另一个进程,正在和你通信";pid_t id = getpid();//向命名管道中写入数据rint wfd = open(FIFOPATH, O_WRONLY, 0666);int cnt = 1;int circnt = 5;while(circnt--){sleep(5);char sig = 'r';ssize_t s = write(wfd, &sig, sizeof(sig));assert(s == sizeof(char));(void)s;snprintf(start, SHMSIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);}//sleep(10);//删除前最好取消关联detachShm(start);close(wfd);//删除共享内存//delShm(shmId);return 0;
}//
//server
#include "comm.hpp"int main()
{//createFifo(FIFOPATH);//先获取key,用于唯一标识共享内存key_t key = getKey(PATHNAME, PROJ_ID);printf("key:0x%x\n", key);//用户创建共享内存int shmId = getShm(key);printf("shmId:%d\n", shmId);//进程与内存关联char* start = (char*)attachShm(shmId);printf("attach success, address start: %p\n", start);//进程通信//如果从命名管道中读到了r,就从共享内存中读取数据int rfd = open(FIFOPATH, O_RDONLY, 0666);while(true){char sig;ssize_t s = read(rfd, &sig, sizeof(sig));if(s == 0){printf("read:%d\n", s);break;}if(sig == 'r' && s > 0){struct shmid_ds sd;shmctl(shmId, IPC_STAT, &sd);printf("client say : %s, cpid[%d], key[0x%x]\n", start, sd.shm_cpid, sd.shm_perm.__key); }sleep(1);}//sleep(9);//删除前最好取消关联detachShm(start);//删除共享内存delShm(shmId);close(rfd);//删除命名管道removeFifo(FIFOPATH);return 0;
}
3.4 共享内存的优点
共享内存是所有通信方式中速度最快的。
因为其能大大降低数据的拷贝次数。
同样的数据通信,管道实现和共享内存实现。考虑键盘、显示器,共享内存会有2+2次拷贝。
只需要将数据从输入拷贝到共享内存,再从内存直接放到标准缓冲区,再到屏幕。
而管道通信,在进入管道多了要经过用户层面上的缓冲区(FILE),多了两次拷贝。
所以如果只考虑管道和共享内存通信传输大量的数据的话,共享内存能快不少。
3.5 信号量以及与共享内存有关的概念
什么是信号量?
信号量本身是一个计数器,通常用来表示公共资源中,资源数量多少的问题的。
公共资源:是可以被多个进程访问的资源。
1、值得注意的是公共资源是需要保护的,不然会出现数据不一致的问题。(而对于数据保护,提出了一些方法,但这些方法也会造成问题,所以问题没有解决,最后只是到了被接受的程度)
2、被保护起来的公共资源称为临界资源。
3、进程访问临界资源的代码,称为临界区,而其它的称为非临界区。
4、共享资源要么是一个整体,要么划分一个一个资源部分。
如何保护共享资源呢?
互斥与同步。
互斥也就是当一方访问时,另一方阻塞。
同步的情况是一种原子性,比如银行的两个账户各有1000元,一个账号向另一个账号转账200时,一方要扣200,另一方要加200,而如果转账失败,要保证各个账户金额不变。
信号量的作用
打个比方,比如电影院买票,一部电影每场次座位的数量就是一种信号量,在人们买票的时候都要先访问这个信号量。
也就是进程在访问公共资源前,都必须申请sem信号量。
相关文章:

【Linux】进程间通信(匿名管道和命名管道通信、共享内存通信)
文章目录1、进程间通信1.1 进程的通信1.2 如何让进程间通信?1.3 进程间通信的本质2、管道通信2.1 匿名管道2.2 匿名管道通信2.3 命名管道2.4 命名管道的通信3、SystemV中的共享内存通信3.1 共享内存3.2 共享内存的通信3.3 共享内存的缺点以及数据保护3.4 共享内存的…...

漏洞分析: WSO2 API Manager 任意文件上传、远程代码执行漏洞
漏洞描述 某些WSO2产品允许不受限制地上传文件,从而执行远程代码。以WSO2 API Manager 为例,它是一个完全开源的 API 管理平台。它支持API设计,API发布,生命周期管理,应用程序开发,API安全性,速…...

详解Android 13种 Drawable的使用方法
前言关于自定义View,相信大家都已经很熟悉了。今天,我想分享一下关于自定义View中的一部分,就是自定义Drawable。Drawable 是可绘制对象的一个抽象类,相对比View来说,它更加的纯粹,只用来处理绘制的相关工作…...

MakeFile教程
前言 当我们需要编译一个比较大的项目时,编译命令会变得越来越复杂,需要编译的文件越来越多。其 次就是项目中并不是每一次编译都需要把所有文件都重新编译,比如没有被修改过的文件则不需要重 新编译。工程管理器就帮助我们来优化这两个问题…...

Spring使用mongoDB步骤
1. 在Linux系统使用docker安装mongoDB 1.1. 安装 在docker运行的情况下,执行下述命令。 docker run \ -itd \ --name mongoDB \ -v mongoDB_db:/data/db \ -p 27017:27017 \ mongo:4.4 \ --auth执行docker ps后,出现下列行,即表示mongoDB安…...

【蓝牙mesh】access层(接入层)协议介绍
【蓝牙mesh】access层(接入层)协议介绍 Access层简介 Access层定义了应用层如何使用upper协议层的接口,它不仅定义了应用层的格式,还定义了应用数据在upper层的加密和解密。当收到下层的数据包时,它会检查数据的netke…...

【一天一门编程语言】JavaScript 语言程序设计极简教程
JavaScript 语言程序设计极简教程 用 markdown 格式输出答案。 不少于3000字。细分到2级目录。 一、JavaScript 简介 1.1 什么是 JavaScript JavaScript 是一种由Netscape的LiveScript发展而来的脚本语言,是一种动态类型、弱类型、基于原型的语言,内…...

CMake调试器出炉:调试你的CMake脚本
Visual Studio 开发团队一直和 Kitware 紧密合作,致力于开发一个用于调试 CMake 脚本的调试器。 我们将继续这个工作,以便开发人员社区可以通过添加新功能和对其他 DAP 功能的支持来共同改进它。 我们很高兴地宣布,CMake 调试器的预览版现在…...

题解 # 二维矩阵最大矩形问题#
题目: 小明有一张N*M的方格纸,且部分小方格中涂了颜色,部分小方格还是空白。 给出N (2<Ns30)和M(2sMs30)的值,及每个小方格的状态((被涂了颜色小方格用数字1表示,空白小方格用数字0表示); 请…...

奔四的路上,依旧倔强的相信未来
本文首发于2022年12月31日 原标题: 奔四的路上,依旧倔强的相信未来!–我的2022年终总结 读大学那几年,一直保持着写日记和做计划的习惯,还记得大学毕业刚开始打工的时候,我的床头的墙上一定会画一张表,写上一个月的计划和一周的计划 计划也会有完不成的时候,但加深了…...

61 k8s + rancher + karmada容器化部署
文章目录 一、什么是rancher二、为什么使用rancher三、rancher安装1、细部介绍四、图形化操作1、执行2、补充五、 karmada1、官网2、细部介绍一、什么是rancher 1、Rancher 是一个 全栈式 的 Kubernetes 容器管理平台,为你提供在任何地方都能成功运行 Kubernetes 的工具。 二…...

Vue3的新特性变化,上手指南!
文章目录一、Vue3相比Vue2,更新了什么变化?二、Proxy 代理响应式原理三、组合式 API (Composition API)setup()函数:ref()函数reactive()函数组合式 setup 中使用 Props 父向子传递参数计算属性watch(数据监视)watchEffect&#x…...

OllyDbg
本文通过吾爱破解论坛上提供的OllyDbg版本为例,讲解该软件的使用方法 F2对鼠标所处的位置打下断点,一般表现为鼠标所属地址位置背景变红F3加载一个可执行程序,进行调试分析,表现为弹出打开文件框F4执行程序到光标处F5缩小还原当前…...

记一次键盘维修,最终修复
我的笔记本是华硕的K45VD,是我亲人在高二那年买的,之后就一直给我用,距今2023年已经差不多13年,它承载了太多记忆。在大学期间也给它升级,重要的零部件基本没问题。只在大学时加了8G内存和一个240G固态,换了…...

LeetCode 155.最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。实现 MinStack 类:MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。int getMin(…...

C++学习笔记-重载运算符和重载函数
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。 C 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重…...

Java —— JDBC
引入mysql链接 创建表格 Navicat查看建表代码双击要打开的表,右侧顶端点击ddl小方框 CREATE TABLE s (id int(6) NOT NULL,name varchar(20) COLLATE utf8_bin DEFAULT NULL,age int(11) DEFAULT NULL,gender varchar(2) COLLATE utf8_bin DEFAULT NULL,dept var…...

备战金三银四,熬夜半个月汇集大厂 Java 岗 1600 页面试真题
如果你不停地加班。却很少冒险,也很少学习,那你极大可能会陷入到内卷中。 为什么这么说呢?我们先来捋清楚「内卷」的概念: 「内卷化」简而言之就是:日复一日,越混越掉坑里。 所谓内卷化,指一种…...

9、面向对象、泛型与反射
目录一、构造函数二、继承与重写三、泛型四、反射1 - 反射的基本概念2 - 反射的基础数据类型3 - 反射APIa - 获取Type类型b - 获取struct成员变量的信息c - 获取struct成员方法的信息d - 获取函数的信息e - 判断类型是否实现了某接口五、reflect.Valuea - 空value判断b - 获取V…...

Python使用百度通用API进行翻译
想汉化StarUML这个软件,感觉工作量太大,想要用Python自动翻译。 结果网上找的一个个用不了,或者用一会儿就断。 于是自己手写了一个简单的,只有两个类:APIConfig和Translater 使用 demo my_api_config APIConfig(…...

JavaScript 弹窗
文章目录JavaScript 弹窗警告框确认框提示框换行JavaScript 弹窗 可以在 JavaScript 中创建三种消息框:警告框、确认框、提示框。 警告框 警告框经常用于确保用户可以得到某些信息。 当警告框出现后,用户需要点击确定按钮才能继续进行操作。 语法 wi…...

408复试day1
文章目录数据结构计算机组成原理操作系统计算机网络数据结构 深度优先遍历DFS: 首先访问图中起始顶点v,然后由v出发,访问与v邻接且未被访问的顶点v1,再访问与v1相邻的且未被访问的顶点v2……重复上述过程。当不能再继续向下访问时…...

gdb openocd jlink arm-a9调试
连接关系是这样的:gdb —> openocd —>(这里需要两个xx.cfg配置文件) jlink —> arm-a9板子 具体流程是这样的: 给jlink(硬件调试器)安装驱动,用USB Driver Tool这个软件,…...

Leetcode Solutions - Part 2
1. Two Sum 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你可以按…...

外盘国际期货:围观那些奇葩的国际节日?
围观那些奇葩的国际节日? 2月24日:世界讨厌香菜日,号召全世界所以讨厌香菜的人一起抵制香菜,2016年世界反香菜联盟 3月21日:世界睡眠日,唤起全民对睡眠重要性的认识,2001年国际精神卫生组织 …...

Kubernetes之服务的基本管理
svc是kubernetes最核心的概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并将请求进行负载分发到后端的各个容器应用上。pod生命周期短不稳定,pod异常后新生成的pod的IP会发生变化,通过…...

TimeWheel时间轮算法原理及实现(附源码)
时间轮算法原理及实现前言1.时间轮核心2.简单定时器3.任务队列4.优化任务队列5.简单时间轮6.多层时间轮前言 在各种业务场景中,我们总是会需要一些定时进行一些操作,这些操作可能是需要在指定的某个时间点操作,也可能是每过一个固定的时间间隔后进行操作,这就要求我们需要有一个…...

【蓝牙mesh】Upper协议层介绍
【蓝牙mesh】Upper协议层介绍 Upper层简介 Upper协议层用于处理网络层以上的功能,包括设备的应用层数据、安全、群组等信息,是实现蓝牙mesh应用功能的关键协议之一。Upper层接收来自Access层的数据或者是Upper层自己生成的Control数据,并且将…...

NEXUS 6P刷机安装Edxposed
刷机 abd等工具下载: https://developer.android.com/studio/releases/platform-tools?hlzh-cn 下载后配置环境变量 镜像下载: https://developers.google.com/android/images?hlzh-cn#angler Magisk下载 GitHub - topjohnwu/Magisk: The Magic M…...

web、ES、vue等知识总结
1:Vue2和vue3的区别: 一个、两个2:Vue3.x为什么要用Proxy来代替Object.defineProperty?3:Proxy4:一些知识点的原理全面5:vue3中加入eslint和prettier6:详解vue中的diff算法7:响应式布局的常用解决方案对比…...