【Linux】Linux进程间通信(三)

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:Linux
🎯长路漫漫浩浩,万事皆有期待

上一篇博客:【Linux】Linux进程概念

文章目录

  • system V进程间通信
    • system V共享内存
      • 共享内存的基本原理
      • 共享内存数据结构
      • 共享内存的建立与释放
      • 共享内存的创建
      • 共享内存的释放
      • 共享内存的关联
      • 共享内存的去关联
      • 用共享内存实现serve&client通信
      • 共享内存与管道进行对比
  • 总结:

system V进程间通信

管道通信本质是基于文件的,也就是说操作系统并没有为此做过多的设计工作,而system V IPC是操作系统特地设计的一种通信方式。但是不管怎么样,它们的本质都是一样的,都是在想尽办法让不同的进程看到同一份由操作系统提供的资源。

system V IPC提供的通信方式有以下三种:

system V共享内存
system V消息队列
system V信号量

其中,system V共享内存和system V消息队列是以传送数据为目的的,而system V信号量是为了保证进程间的同步与互斥而设计的,虽然system V信号量和通信好像没有直接关系,但属于通信范畴。

说明一下:
system V共享内存和system V消息队列就类似于手机,用于沟通信息;system V信号量就类似于下棋比赛时用的棋钟,用于保证两个棋手之间的同步与互斥。

system V共享内存

共享内存的基本原理

共享内存让不同进程看到同一份资源的方式就是,在物理内存当中申请一块内存空间,然后将这块内存空间分别与各个进程各自的页表之间建立映射,再在虚拟地址空间当中开辟空间并将虚拟地址填充到各自页表的对应位置,使得虚拟地址和物理地址之间建立起对应关系,至此这些进程便看到了同一份物理内存,这块物理内存就叫做共享内存。
在这里插入图片描述

注意:
这里所说的开辟物理空间、建立映射等操作都是调用系统接口完成的,也就是说这些动作都由操作系统来完成。

共享内存数据结构

在系统当中可能会有大量的进程在进行通信,因此系统当中就可能存在大量的共享内存,那么操作系统必然要对其进行管理,所以共享内存除了在内存当中真正开辟空间之外,系统一定还要为共享内存维护相关的内核数据结构。

共享内存的数据结构如下:

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 */
};

当我们申请了一块共享内存后,为了让要实现通信的进程能够看到同一个共享内存,因此每一个共享内存被申请时都有一个key值,这个key值用于标识系统中共享内存的唯一性。
可以看到上面共享内存数据结构的第一个成员是shm_perm,shm_perm是一个ipc_perm类型的结构体变量,每个共享内存的key值存储在shm_perm这个结构体变量当中,其中ipc_perm结构体的定义如下:

struct ipc_perm{__kernel_key_t  key;__kernel_uid_t  uid;__kernel_gid_t  gid;__kernel_uid_t  cuid;__kernel_gid_t  cgid;__kernel_mode_t mode;unsigned short  seq;
};

记录一下:
共享内存的数据结构shmid_ds和ipc_perm结构体分别在/usr/include/linux/shm.h和/usr/include/linux/ipc.h中定义。

共享内存的建立与释放

共享内存的建立大致包括以下两个过程:

在物理内存当中申请共享内存空间。
将申请到的共享内存挂接到地址空间,即建立映射关系。

共享内存的释放大致包括以下两个过程:

将共享内存与地址空间去关联,即取消映射关系。
释放共享内存空间,即将物理内存归还给系统。

共享内存的创建

创建共享内存我们需要用shmget函数,shmget函数的函数原型如下:

int shmget(key_t key, size_t size, int shmflg);

shmget函数的参数说明:

第一个参数key,表示待创建共享内存在系统当中的唯一标识。
第二个参数size,表示待创建共享内存的大小。
第三个参数shmflg,表示创建共享内存的方式。

shmget函数的返回值说明:

shmget调用成功,返回一个有效的共享内存标识符(用户层标识符)。
shmget调用失败,返回-1。

注意: 我们把具有标定某种资源能力的东西叫做句柄,而这里shmget函数的返回值实际上就是共享内存的句柄,这个句柄可以在用户层标识共享内存,当共享内存被创建后,我们在后续使用共享内存的相关接口时,都是需要通过这个句柄对指定共享内存进行各种操作。

传入shmget函数的第一个参数key,需要我们使用ftok函数进行获取

ftok函数的函数原型如下:

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

ftok函数的作用就是,将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,在使用shmget函数获取共享内存时,这个key值会被填充进维护共享内存的数据结构当中。需要注意的是,pathname所指定的文件必须存在且可存取。

注意:

使用ftok函数生成key值可能会产生冲突,此时可以对传入ftok函数的参数进行修改。
需要进行通信的各个进程,在使用ftok函数获取key值时,都需要采用同样的路径名和和整数标识符,进而生成同一种key值,然后才能找到同一个共享资源。

传入shmget函数的第三个参数shmflg,常用的组合方式有以下两种:

组合方式					作用
IPC_CREAT				如果内核中不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则直接返回该共享内存的句柄
IPC_CREAT | IPC_EXCL	如果内核中不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则出错返回

换句话说:

使用组合IPC_CREAT,一定会获得一个共享内存的句柄,但无法确认该共享内存是否是新建的共享内存。
使用组合IPC_CREAT | IPC_EXCL,只有shmget函数调用成功时才会获得共享内存的句柄,并且该共享内存一定是新建的共享内存。
至此我们就可以使用ftok和shmget函数创建一块共享内存了,创建后我们可以将共享内存的key值和句柄进行打印,以便观察,代码如下:

#include <stdio.h>
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <unistd.h>#define PATHNAME "/home/sherry/11.10/shm/server.c" //路径名#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096 //共享内存的大小int main()
{key_t key = ftok(PATHNAME, PROJ_ID); //获取key值if (key < 0){perror("ftok");return 1;}int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL); //创建新的共享内存if (shm < 0){perror("shmget");return 2;}printf("key: %x\n", key); //打印key值printf("shm: %d\n", shm); //打印句柄return 0;
}

该代码编写完毕运行后,我们可以看到输出的key值和句柄值:
在这里插入图片描述

在Linux当中,我们可以使用ipcs命令查看有关进程间通信设施的信息。
在这里插入图片描述

单独使用ipcs命令时,会默认列出消息队列、共享内存以及信号量相关的信息,若只想查看它们之间某一个的相关信息,可以选择携带以下选项:

-q:列出消息队列相关信息。
-m:列出共享内存相关信息。
-s:列出信号量相关信息。

例如,携带-m选项查看共享内存相关信息:
在这里插入图片描述

此时,根据ipcs命令的查看结果和我们的输出结果可以确认,共享内存已经创建成功了。

ipcs命令输出的每列信息的含义如下:

标题	含义
key	系统区别各个共享内存的唯一标识
shmid	共享内存的用户层id(句柄)
owner	共享内存的拥有者
perms	共享内存的权限
bytes	共享内存的大小
nattch	关联共享内存的进程数
status	共享内存的状态

注意: key是在内核层面上保证共享内存唯一性的方式,而shmid是在用户层面上保证共享内存的唯一性,key和shmid之间的关系类似于fd和FILE*之间的的关系。

共享内存的释放

通过上面创建共享内存的实验可以发现,当我们的进程运行完毕后,申请的共享内存依旧存在,并没有被操作系统释放。实际上,管道是生命周期是随进程的,而共享内存的生命周期是随内核的,也就是说进程虽然已经退出,但是曾经创建的共享内存不会随着进程的退出而释放。

这说明,如果进程不主动删除创建的共享内存,那么共享内存就会一直存在,直到关机重启(system V IPC都是如此),同时也说明了IPC资源是由内核提供并维护的。

此时我们若是要将创建的共享内存释放,有两个方法,一就是使用命令释放共享内存,二就是在进程通信完毕后调用释放共享内存的函数进行释放。

使用命令释放共享内存资源

我们可以使用ipcrm -m shmid命令释放指定id的共享内存资源。

ipcrm -m 0

在这里插入图片描述

注意: 指定删除时使用的是共享内存的用户层id,即列表当中的shmid。

使用程序释放共享内存资源

控制共享内存我们需要用shmctl函数,shmctl函数的函数原型如下:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmctl函数的参数说明:

第一个参数shmid,表示所控制共享内存的用户级标识符。
第二个参数cmd,表示具体的控制动作。
第三个参数buf,用于获取或设置所控制共享内存的数据结构。

shmctl函数的返回值说明:

shmctl调用成功,返回0。
shmctl调用失败,返回-1。
其中,作为shmctl函数的第二个参数传入的常用的选项有以下三个:

选项			作用
IPC_STAT	获取共享内存的当前关联值,此时参数buf作为输出型参数
IPC_SET		在进程有足够权限的前提下,将共享内存的当前关联值设置为buf所指的数据结构中的值
IPC_RMID	删除共享内存段

例如,在以下代码当中,共享内存被创建,两秒后程序自动移除共享内存,再过两秒程序就会自动退出。

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>#define PATHNAME "/home/sherry/11.10/shm/server.c" //路径名#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096 //共享内存的大小int main()
{key_t key = ftok(PATHNAME, PROJ_ID); //获取key值if (key < 0){perror("ftok");return 1;}int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL); //创建新的共享内存if (shm < 0){perror("shmget");return 2;}printf("key: %x\n", key); //打印key值printf("shm: %d\n", shm); //打印句柄sleep(2);shmctl(shm, IPC_RMID, NULL); //释放共享内存sleep(2);return 0;
}

我们可以在程序运行时,使用以下监控脚本时刻关注共享内存的资源分配情况:

while :; do ipcs -m;echo "###################################";sleep 1;done

通过监控脚本可以确定共享内存确实创建并且成功释放了。

在这里插入图片描述

共享内存的关联

将共享内存连接到进程地址空间我们需要用shmat函数,shmat函数的函数原型如下:

void *shmat(int shmid, const void *shmaddr, int shmflg);

shmat函数的参数说明:

第一个参数shmid,表示待关联共享内存的用户级标识符。
第二个参数shmaddr,指定共享内存映射到进程地址空间的某一地址,通常设置为NULL,表示让内核自己决定一个合适的地址位置。
第三个参数shmflg,表示关联共享内存时设置的某些属性。
shmat函数的返回值说明:

shmat调用成功,返回共享内存映射到进程地址空间中的起始地址。
shmat调用失败,返回(void*)-1。
其中,作为shmat函数的第三个参数传入的常用的选项有以下三个:

选项			作用
SHM_RDONLY	关联共享内存后只进行读取操作
SHM_RND		若shmaddr不为NULL,则关联地址自动向下调整为SHMLBA的整数倍。公式:shmaddr-(shmaddr%SHMLBA)
0			默认为读写权限

这时我们可以尝试使用shmat函数对共享内存进行关联。

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>#define PATHNAME "/home/sherry/11.10/shm/server.c" //路径名#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096 //共享内存的大小int main()
{key_t key = ftok(PATHNAME, PROJ_ID); //获取key值if (key < 0){perror("ftok");return 1;}int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL); //创建新的共享内存if (shm < 0){perror("shmget");return 2;}printf("key: %x\n", key); //打印key值printf("shm: %d\n", shm); //打印句柄printf("attach begin!\n");sleep(2);char* mem = shmat(shm, NULL, 0); //关联共享内存if (mem == (void*)-1){perror("shmat");return 1;}printf("attach end!\n");sleep(2);shmctl(shm, IPC_RMID, NULL); //释放共享内存return 0;
}

代码运行后发现关联失败,主要原因是我们使用shmget函数创建共享内存时,并没有对创建的共享内存设置权限,所以创建出来的共享内存的默认权限为0,即什么权限都没有,因此server进程没有权限关联该共享内存。
在这里插入图片描述

我们应该在使用shmget函数创建共享内存时,在其第三个参数处设置共享内存创建后的权限,权限的设置规则与设置文件权限的规则相同。

int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建权限为0666的共享内存

此时再运行程序,即可发现关联该共享内存的进程数由0变成了1,而共享内存的权限显示也不再是0,而是我们设置的666权限。

共享内存的去关联

取消共享内存与进程地址空间之间的关联我们需要用shmdt函数,shmdt函数的函数原型如下:

int shmdt(const void *shmaddr);

shmdt函数的参数说明:

待去关联共享内存的起始地址,即调用shmat函数时得到的起始地址。
shmdt函数的返回值说明:

shmdt调用成功,返回0。
shmdt调用失败,返回-1。
现在我们就能够取消共享内存与进程之间的关联了。

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>#define PATHNAME "/home/cl/Linuxcode/IPC/shm/server.c" //路径名#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096 //共享内存的大小int main()
{key_t key = ftok(PATHNAME, PROJ_ID); //获取key值if (key < 0){perror("ftok");return 1;}int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建新的共享内存if (shm < 0){perror("shmget");return 2;}printf("key: %x\n", key); //打印key值printf("shm: %d\n", shm); //打印句柄printf("attach begin!\n");sleep(2);char* mem = shmat(shm, NULL, 0); //关联共享内存if (mem == (void*)-1){perror("shmat");return 1;}printf("attach end!\n");sleep(2);printf("detach begin!\n");sleep(2);shmdt(mem); //共享内存去关联printf("detach end!\n");sleep(2);shmctl(shm, IPC_RMID, NULL); //释放共享内存return 0;
}

运行程序,通过监控即可发现该共享内存的关联数由1变为0的过程,即取消了共享内存与该进程之间的关联。

注意: 将共享内存段与当前进程脱离不等于删除共享内存,只是取消了当前进程与该共享内存之间的联系。

用共享内存实现serve&client通信

在知道了共享内存的创建、关联、去关联以及释放后,现在可以尝试让两个进程通过共享内存进行通信了。在让两个进程进行通信之前,我们可以先测试一下这两个进程能否成功挂接到同一个共享内存上。

服务端负责创建共享内存,创建好后将共享内存和服务端进行关联,之后进入死循环,便于观察服务端是否挂接成功。

服务端代码如下:

//server.c
#include "comm.h"int main()
{key_t key = ftok(PATHNAME, PROJ_ID); //获取key值if (key < 0){perror("ftok");return 1;}int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建新的共享内存if (shm < 0){perror("shmget");return 2;}printf("key: %x\n", key); //打印key值printf("shm: %d\n", shm); //打印共享内存用户层idchar* mem = shmat(shm, NULL, 0); //关联共享内存while (1){//不进行操作}shmdt(mem); //共享内存去关联shmctl(shm, IPC_RMID, NULL); //释放共享内存return 0;
}

客户端只需要直接和服务端创建的共享内存进行关联即可,之后也进入死循环,便于观察客户端是否挂接成功。

客户端代码如下:

//client.c
#include "comm.h"int main()
{key_t key = ftok(PATHNAME, PROJ_ID); //获取与server进程相同的key值if (key < 0){perror("ftok");return 1;}int shm = shmget(key, SIZE, IPC_CREAT); //获取server进程创建的共享内存的用户层idif (shm < 0){perror("shmget");return 2;}printf("key: %x\n", key); //打印key值printf("shm: %d\n", shm); //打印共享内存用户层idchar* mem = shmat(shm, NULL, 0); //关联共享内存int i = 0;while (1){//不进行操作}shmdt(mem); //共享内存去关联return 0;
}

为了让服务端和客户端在使用ftok函数获取key值时,能够得到同一种key值,那么服务端和客户端传入ftok函数的路径名和和整数标识符必须相同,这样才能生成同一种key值,进而找到同一个共享资源进行挂接。这里我们可以将这些需要共用的信息放入一个头文件当中,服务端和客户端共用这个头文件即可。

共用头文件的代码如下:

//comm.h
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>#define PATHNAME "/home/cl/Linuxcode/IPC/shm/server.c" //路径名#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096 //共享内存的大小

先后运行服务端和客户端后,通过监控脚本可以看到服务端和客户端所关联的是同一个共享内存,共享内存关联的进程数也是2,表示服务端和客户端挂接共享内存成功。

此时我们就可以让服务端和客户端进行通信了,这里以简单的发送字符串为例。

客户端不断向共享内存写入数据:

//客户端不断向共享内存写入数据
int i = 0;
while (1){mem[i] = 'A' + i;i++;mem[i] = '\0';sleep(1);
}

服务端不断读取共享内存当中的数据并输出:

//服务端不断读取共享内存当中的数据并输出
while (1){printf("client# %s\n", mem);sleep(1);
}

此时先运行服务端创建共享内存,当我们运行客户端时服务端就开始不断输出数据,说明服务端和客户端是能够正常通信的。

共享内存与管道进行对比

当共享内存创建好后就不再需要调用系统接口进行通信了,而管道创建好后仍需要read、write等系统接口进行通信。实际上,共享内存是所有进程间通信方式中最快的一种通信方式。

我们先来看看管道通信:
在这里插入图片描述

从这张图可以看出,使用管道通信的方式,将一个文件从一个进程传输到另一个进程需要进行四次拷贝操作:

服务端将信息从输入文件复制到服务端的临时缓冲区中。
将服务端临时缓冲区的信息复制到管道中。
客户端将信息从管道复制到客户端的缓冲区中。
将客户端临时缓冲区的信息复制到输出文件中。
我们再来看看共享内存通信:
在这里插入图片描述

从这张图可以看出,使用共享内存进行通信,将一个文件从一个进程传输到另一个进程只需要进行两次拷贝操作:

从输入文件到共享内存。
从共享内存到输出文件。
所以共享内存是所有进程间通信方式中最快的一种通信方式,因为该通信方式需要进行的拷贝次数最少。

但是共享内存也是有缺点的,我们知道管道是自带同步与互斥机制的,但是共享内存并没有提供任何的保护机制,包括同步与互斥。

总结:

今天我们继续学习了Linux进程间通信的相关知识,了解了system V进程间通信等 。接下来,我们将继续学习Linux的其他知识。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python:函数篇(每周练习)

编程题&#xff1a; Python第四章作业&#xff08;初级&#xff09; (educoder.net) 题一&#xff1a;无参无返回值函数 def print_hi_human(): # 函数名用小写字母print("人类&#xff0c;你好&#xff01;")if __name__ __main__:print_hi_human() 题二&#…

在python中os.chdir()的含义以及用法

文章目录 一、os.chdir() 是什么&#xff1f;二、用法注意 一、os.chdir() 是什么&#xff1f; 在Python中&#xff0c;os.chdir() 是 “change directory” 的缩写&#xff0c;意思是改变当前工作目录。这个函数是Python的 os 模块的一部分&#xff0c;允许你更改程序的工作目…

2023.11.16 hivesql高阶函数之开窗函数

目录 1.开窗函数的定义 2.数据准备 3.开窗函数之排序 需求:用三种排序方法查询学生的语文成绩排名,并降序显示 4.开窗函数分组 需求:按照科目来分类,使用三种排序方式来排序学生的成绩 5.聚合函数与分组配合使用 6.聚合函数同时和分组以及排序关键字配合使用 --需求1&…

MAC电脑连接外接显示屏,颜色显示有问题,又粉、紫色蒙版,问题处理(1)

问题描述 买了一个显示器&#xff0c;想给mac做分屏使用&#xff0c;结果连上之后发现&#xff0c;整个屏幕像是被蒙上了一层紫色的蒙版。 就像下面展示的一样&#xff1a; 解决 将显示器颜色空间改为RGB颜色空间即可。 打开显示器菜单&#xff0c;找到颜色空间选项&#…

12.Oracle的索引

Oracle11g的索引 一、什么是索引二、索引的分类三、索引的语法四、分析索引四、索引的作用及使用场景 一、什么是索引 在Oracle数据库中&#xff0c;索引是一种特殊的数据结构&#xff0c;用于提高查询性能和加速数据检索。索引存储了表中某列的值和对应的行指针&#xff0c;这…

初学编程学习,计算机编程怎么自学,中文编程工具下载

初学编程学习&#xff0c;计算机编程怎么自学&#xff0c;中文编程工具下载 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的软件&#xff0c;象如…

Uniapp连接iBeacon设备——实现无线定位与互动体验(理论篇)

目录 前言&#xff1a; 一、什么是iBeacon技术 二、Uniapp连接iBeacon设备的准备工作 硬件设备&#xff1a; 三、Uniapp连接iBeacon设备的实现步骤 创建Uniapp项目&#xff1a; 四、Uniapp连接iBeacon设备的应用场景 室内导航&#xff1a; 五、Uniapp连接iBeacon设备的未来…

Hangfire.Pro 3.0 Crack

Hangfire.Pro 有限的存储支持 Hangfire Pro 是一组扩展包&#xff0c;允许使用批处理创建复杂的后台作业工作流程&#xff0c;并提供对超快速Redis作为作业存储的支持 请注意&#xff0c;仅在使用Hangfire.SqlServer、Hangfire.Pro.Redis或Hangfire.InMemory包作为作业存储时才…

解决 vite 4 开发环境和生产环境打包后空白、配置axios跨域、nginx代理本地后端接口问题

1、解决打包本地无法访问空白 首先是pnpm build 打包后直接在dist访问&#xff0c;是访问不了的&#xff0c;需要开启服务 终端输入 npm install -g serve 然后再输入 serve -s dist 就可以访问了 但要保证 路由模式是&#xff1a;createWebHashHistory 和vite.conffig.j…

Java的XWPFTemplate word生成列表

Java的XWPFTemplate工具类导出word.docx的使用_xwpftemplate 语法_youmdt的博客-CSDN博客 如果是表格的列表参考上面这篇文章即可&#xff0c;比较复杂的列表遍历暂时还没找到方法&#xff0c;只能手动创建表格了 上面是模板&#xff0c;非常简单&#xff0c;以为我们是要自己创…

八大学习方法(金字塔模型、费曼学习法、布鲁姆学习模型)

在微博上看到博主发的&#xff0c;觉得总结很好&#xff0c;在此摘录&#xff1a;

如何在远程协同视频会议中确保安全性?

随着远程工作的普及&#xff0c;远程协同视频会议已成为企业和团队之间进行交流和协作的重要工具。与此同时&#xff0c;会议中的安全性问题也日益凸显。本文将介绍如何在远程协同视频会议中确保安全性&#xff0c;主要包括以下方面&#xff1a; 1、内网部署 将会议服务器部署…

CF1899C Yarik and Array(DP,贪心)

题目链接 题目 A subarray is a continuous part of array. Yarik recently found an array a of n elements and became very interested in finding the maximum sum of a non empty subarray. However, Yarik doesn’t like consecutive integers with the same parity, s…

MIB 6.1810实验Xv6 and Unix utilities(5)find

难度:moderate Write a simple version of the UNIX find program for xv6: find all the files in a directory tree with a specific name. Your solution should be in the file user/find.c. 题目要求&#xff1a;实现find &#xff0c;即在某个路径中&#xff0c;找出某…

windows 安装 Oracle Database 19c

目录 什么是 Oracle 数据库 下载 Oracle 数据库 解压文件 运行安装程序 测试连接 什么是 Oracle 数据库 Oracle数据库是由美国Oracle Corporation&#xff08;甲骨文公司&#xff09;开发和提供的一种关系型数据库管理系统&#xff0c;它是一种强大的关系型数据库管理系统…

Excel 文件比较工具 xlCompare 11.01 Crack

比较两个 Excel 文件之间的差异 xlCompare. xlCompare.com 是性能最佳的 Excel diff 工具&#xff0c;用于比较两个 Excel 文件或工作表并在线突出显示差异。xlCompare 包括免费的在线 Excel 和 CSV 文件比较服务以及用于比较和合并 Excel 文件的强大桌面工具。如果您想在线了…

基于蝠鲼觅食算法优化概率神经网络PNN的分类预测 - 附代码

基于蝠鲼觅食算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于蝠鲼觅食算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于蝠鲼觅食优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

贝加莱MQTT功能

贝加莱实现MQTT Client端的功能库和例程 导入库和例程&#xff0c;AS Logical View中分别通过Add Object—Library&#xff0c;Add—Program插入MQTT库和例程。 将例程Sample放置于CPU循环周期中 定义证书存放路径&#xff0c;在AS Physical View 中&#xff0c;右击PLC—Con…

R语言爬虫程序自动爬取图片并下载

R语言本身并不适合用来爬取数据&#xff0c;它更适合进行统计分析和数据可视化。而Python的requests&#xff0c;BeautifulSoup&#xff0c;Scrapy等库则更适合用来爬取网页数据。如果你想要在R中获取网页内容&#xff0c;你可以使用rvest包。 以下是一个简单的使用rvest包爬取…

CF1899B 250 Thousand Tons of TNT

题目链接 题目 题目大意 T T T 组测试数据 每组 n n n 个货物&#xff0c;第 i i i 个货物 的重量是 a i a_i ai​ 用k辆货车按顺序装这些货物&#xff0c;条件是每辆车上的货物个数都一样&#xff0c;也即是说 n n n 必须能被 k k k 整除&#xff0c; 求任意两辆车货物总…