【Linux】进程通信 — 共享内存

文章目录

  • 📖 前言
  • 1. 共享内存
  • 2. 创建共享内存
    • 2.1 ftok()创建key值:
    • 2.2 shmget()创建共享内存:
    • 2.3 ipcs指令:
    • 2.4 shmctl()接口:
    • 2.5 shmat()/shmdt()接口:
    • 2.6 共享内存没有访问控制:
    • 2.7 通过管道对共享内存进行控制:
  • 3. 相关概念

📖 前言

上一章我们由进程通信,引入并讲述了管道,匿名管道和命名管道和匿名管道。本章我们将继续讲解进程通信的另一种方式,通过共享内存的方式来进行进程间的通信。还要学习几个系统调用接口,并用代码实现两个进程通过共享内存来进行通信。目标已经确定,接下来就要搬好小板凳,准备开讲了…🙆🙆🙆🙆

本章讲的是system V的共享内存。


1. 共享内存

之前我们学习进程地址空间时就已经对进程地址空间的构成有了相对的认识【进程地址空间 - 复习】:

在这里插入图片描述

  • 我们知道堆栈相对而生,堆从低地址向高地址生长,栈从高地址向低地址生长,而在这两块空间之间的则是共享区
  • 堆栈之间的区域特别大,堆栈之间的区域称为共享区,其中共享库就在这里。

假设有种接口:

  1. 能在物理内存创建空间。
  2. 通过两个进程调用接口,然后物理内存中的空间映射到自己的地址空间上,然后将空间的起始地址返回给用户,此时用户就能通过页表找到物理内存的空间。

此时两个进程各自都完成了,第一步创建共享内存,第二步分别将共享内存挂接到各自的进程上下文里面。

  • 进程间通信的前提是:先让不同的进程,看到同一份资源!
  • 共享内存是一种进程间通信机制,它允许多个进程共享同一块物理内存区域(就能同时看到一份资源)。
  • 当一个进程向共享内存写入数据时,实际上是将数据直接写入到共享内存所对应的物理内存中。
  • 其他进程可以通过读取相同的共享内存区域来获取已写入的数据。

优点:

  • 与其他进程间通信方式(如管道、消息队列等)不同。
  • 共享内存避免了数据的复制和传输过程,因此具有较高的效率。
  • 它可以提供快速的数据交换,特别适用于需要频繁共享大量数据的进程间通信场景。

2. 创建共享内存

2.1 ftok()创建key值:

  • 共享内存存在哪里?
  • 内核中 —— 内核会给我们维护共享内存的结构!
  • 共享内存也要被管理起来!一定是先描述,再组织!
    • 系统中可能有很多对进程都在用共享内存通信,所以操作系统也要将它们管理起来。
  • 我怎么知道,这个共享内存是存在还是不存在?
  • 先有方法,标识共享内存的唯一性!
  • 共享内存,在内核中,让不同的进程看到同一份共享内存。
  • 做法是:让他们拥有同一个key即可!

解释:

  • 每个共享内存都是由一个唯一的key值来标识的。在操作系统中,共享内存是进程间进行通信的一种方式,它允许多个进程共享同一块物理内存区域。
  • 为了实现这种共享,操作系统会给每个共享内存区域分配一个唯一的key值来标识它,其他进程可以通过这个key值来访问并操作该共享内存区域。
  • 这样就可以实现多个进程之间的数据交换和共享。

在共享内存里只要保证这个key是唯一的就可以了。至于这个key是多少不重要。

创建key值的函数:

在这里插入图片描述
Linux系统给定了ftok接口,将用户提供的pathname工作路径,以及proj_id项目编号转换为一个共享内存的key(其实就是int类型)。

  • 底层会将第一个参数对应路径文件的inode和指定的项目id这两个数字做组合形成一个唯一值,返回给一个key。
  • 底层是一些列的算法设计,相当于是帮我们构建具有唯一性的数字就可以了。

返回值:

在这里插入图片描述
成功了返回key值,失败了就返回-1。

2.2 shmget()创建共享内存:

使用前提是要有一个唯一的key值:

在这里插入图片描述

  • 第二个参数建议设置成页(4KB)的整倍数:
  • 操作系统和磁盘lO的基本单位大小是4KB。
  • 从磁盘拷贝到内存是以4KB为单位拷贝的。
  • 所以共享内存在申请大小时,也一定是4KB的整数倍。
  • 操作系统只会按照整4KB来申请共享内存,是向上取整。

如果我们申请的是4097,那么操作系统申请的是8KB,但是我们只能用4097,因为只要了这么多。

  • shmflg的设置:
  • IPC_CREAT:创建共享内存,如果已经存在,就获取之,不存在就创建之。
    • 获取:可以获取成功,但是不知道这个共享内存,是本进程创建还是别的进程创建的拿过来的。
  • IPC_EXCL:必须配合IPC_CREAT使用(用按位或|配合使用),如果不存在指定的共享内存,创建之,如果存在了,出错返回。
    • 核心作用: 可以保证,如果shmget函数调用成功,一定是一个全新的share memory(共享内存)。
    • 如果这两个选项合着一起配合使用,一起传, 只要函数调用成功了,得到的一定是个全新的共享内存。
  • 0666或权限值:指定共享内存的访问权限。这些权限值使用八进制表示,例如0666表示可读可写权限。

返回值:

在这里插入图片描述
如果成功的话返回一个标志符号,如果失败的话返回-1,errno被设置。

  • key值是由谁提供:
  • key值得由用户来提供。
  • 如果key值是由操作系统统一提供的话:
    • 那么调用shmget接口,获取共享内存的时候,操作系统给一个key值。
    • 另一个进程如何知道该进程key值呢??是不可能知道的。
    • 那么就不能确定唯一性了,那么不同的应用程序可能会得到相同的key。
    • 这样一来,就有可能导致两个或多个应用程序之间共享同一个共享内存段。
    • 这样的共享可能会引发数据混乱、冲突以及安全性问题。
  • 如果key值是由用户提供的话:
    • 那么一个进程就可以提供一个key值让操作系统帮它创建一个共享内存,并且约定好让其他进程也使用同样的key值。
    • 这二者只要有一个创建了共享内存,能够将key值设置在内核里,那另一个进程则可以用同样的key值找,就可以看到同一个共享内存了。
    • 这就叫做看到了同一份共享资源了。

2.3 ipcs指令:

通过上述讲解,我们只要获取到唯一的key值,调用shmget函数就能获得共享内存了,创建好了我们怎样才能知道有哪些IPC资源呢?

看一下ipcs指令的几个选项:

ipcs -c #查看消息队列/共享内存/信号量
ipcs -s #单独查看信号量
ipcs -q #单独查看消息队列
ipcs -m #单独查看共享内存

ipcs -m查看shm list

在这里插入图片描述

  • key值是十六进制,我们创建出来看到的则是十进制。
  • nattch是挂接上的进程的数量。
  • bytes是申请的共享内存大小。

在这里插入图片描述
ipcrm -m 删除共享内存:

  • 只要在指令后跟上自己要删除的共享内存的shmid值就可以了

在这里插入图片描述

如果不显示删除共享内存那么就只能通过重启云主机的方式来删除共享内存了。

为什么删除共享内存要用shmid而不用key呢?

  • key是操作系统中用来标识共享内存唯一性的, 在用户层用的是shmid
  • 在用户层访问共享内存只能用数字shmid

2.4 shmctl()接口:

shmctl这个函数可以用于操作共享内存:

在这里插入图片描述

  • 第一个参数:想对哪个共享内存做操作。
  • 第二个参数:想对这个共享内存做什么操作。
  • 第三个参数:如果IPC_RMID如果被设置了,shmid_ds设置成nullptr就可以了,还有一些其他的属性设置。

第二个参数可以是:

  • IPC_STAT:用于获取共享内存段的状态信息,包括共享内存段大小、访问权限等。
  • IPC_SET:用于设置共享内存段的状态信息,如更改访问权限、起始地址等。
  • IPC_RMID:用于删除共享内存段,释放相关的资源。

此外,第二个参数还可以与其他标志进行按位或操作,以指定额外的选项或标志,例如:

  • IPC_INFO:用于获取当前共享内存资源的统计信息。
  • SHM_INFO:用于获取当前系统上共享内存段的信息。
  • SHM_STAT:用于获取当前共享内存段的详细信息。

2.5 shmat()/shmdt()接口:

在这里插入图片描述

  • at是attach的简写,把一个共享内存附在进程上。
  • dt是detach的简写,把一个共享内存从进程上脱离下来(去关联)。

返回值:

在这里插入图片描述

  • 因为shmat函数的返回值是一个void*类型的指针,所以我们就可以像使用malloc一样的方式使来挂接共享内存了。
  • 随后对这个共享内存的操作就像平时使用数组一样的方式来使用了。

同样的道理另一个进程也需要挂接这个共享内存才能实现两个进程通过共享内存来进行通信。

注意:

  • 共享内存是一种特殊的内存区域,可以由多个进程同时访问。
  • 尽管共享内存由一个进程创建,但它并不属于任何一个特定的进程。

2.6 共享内存没有访问控制:

在我们之前的学习中知道,管道是有访问控制的进程通信方式,当写端没有写入数据的时候(空管道),读端的read接口会进行等待,直到有管道中有数据写入。

而共享内存的申请更想是我们直接向操作一个malloc出来的空间一样,进程只要有权限,就可以直接拿来用,不向管道那样是个文件,还要通过read/write接口来访问。所以操作系统没有办法帮我们进行访问控制

管道:

  • 首先将数据从外设拷贝到进程上下文代码里面。
  • 再把代码拷贝到管道里面,再调用write把数据拷贝到另一个进程的上下文。
  • 再继续还要将数据拷贝到外设里,一共至少要经历四次拷贝。

在这里插入图片描述
共享内存:

  • 写到共享内存里,对方立马能看到。
  • 用共享内存,如果不考虑外设,最多拷贝一次,一方写入,另一方立马能看到。

在这里插入图片描述

2.7 通过管道对共享内存进行控制:

Log.hpp:

#pragma once#include <iostream>
#include <ctime>std::ostream &Log()
{std::cout << "For Debug | " << "timestamp: " << (uint64_t)time(nullptr) << " | ";return std::cout;
}

Comm.hpp:

#pragma once#include <iostream>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include "Log.hpp"using namespace std;#define PATH_NAME "/home/Zh_Ser/linux"
#define PROJ_ID 0x14
#define MEM_SIZE 4096#define FIFO_FILE "./.fifo"// hpp是将头文件和源文件写在一起的方式
// 函数的定义可以放在里面,一般在开源的项目里用key_t CreateKey()
{key_t key = ftok(PATH_NAME, PROJ_ID);if (key < 0){cerr << "ftok: " << strerror(errno) << endl;exit(1);}return key;
}// 创建命名管道
void CreatFifo()
{umask(0);if (mkfifo(FIFO_FILE, 0666) < 0){Log() << strerror(errno) << endl;exit(2);}
}#define READER O_RDONLY
#define WRITER O_WRONLYint Open(const string& filename, int flags)
{return open(filename.c_str(), flags);
}int Wait(int fd)
{uint32_t values = 0;ssize_t s = read(fd, &values, sizeof(values));return s;
}void Signal(int fd)
{uint32_t cmd = 1;int s = write(fd, &cmd, sizeof(cmd));
}void Close(int fd, const string filename)
{close(fd);unlink(filename.c_str());
}

IpcShmCli.cpp:

#include "Comm.hpp"
#include "Log.hpp"// 充当使用共享内存的角色
int main()
{int fd = Open(FIFO_FILE, WRITER);cout << "Client: " << fd << endl;// 创建相同的key值key_t key = CreateKey();Log() << "key: " << key << endl;// 等待Server端先创建共享内存sleep(1);// 获取共享内存int shmid = shmget(key, MEM_SIZE, IPC_CREAT);if (shmid < 0){Log() << "IpcShmCli shmget: " << strerror(errno) << endl;return 2;}// 挂接char* str = (char*)shmat(shmid, nullptr, 0);// sleep(5);// 用它// 用共享内存,竟然没有使用任何系统调用接口// 直接向str空间写入while (true){printf("Please Enter# ");fflush(stdout);// 往共享内存写数据ssize_t s = read(0, str, MEM_SIZE);if (s > 0){str[s] = '\0';}Signal(fd);}// 去关联shmdt(str);// 不需要删除return 0;
}

IpcShmSer.cpp:

#include "Comm.hpp"
#include "Log.hpp"// 创建全新的共享内存
const int flags = IPC_CREAT | IPC_EXCL;// 充当创建共享内存的角色
int main()
{// 创建管道CreatFifo();int fd = Open(FIFO_FILE, READER);cout << "Server: " << fd << endl;assert(fd >= 0);// 创建Keykey_t key = CreateKey();Log() << "key: " << key << endl;Log() << "create share memory begin" << endl;int shmid = shmget(key, MEM_SIZE, flags | 0666);if (shmid < 0){Log() << "IpcShmSer shmget: " << strerror(errno) << endl;return 2;}Log() << "create shm success, shmid: " << shmid << endl;// 1. 将共享内存和自己的进程产生关联attchchar* str = (char*)shmat(shmid, nullptr, 0);Log() << "attach shm: " << shmid << "success" << endl;// 用它// 服务器端直接用while (true){sleep(1);// 在管道当中等,让读端进行等待if (Wait(fd) <= 0) break;// 从共享内存里读数据printf("%s\n", str);}// 2. 去关联shmdt的返回值就是shmat的返回值shmdt(str);Log() << "detach shm: " << shmid << "success" << endl;// 删除shmctl(shmid, IPC_RMID, nullptr);Log() << "delete shm: " << shmid << "success" << endl;Close(fd, FIFO_FILE);return 0;
}

我们先执行服务端,再执行用户端,我们会发现一个现象,服务端会等待,当用户端启动后两边才开始都跑起来。

这是open接口的阻塞等待:(重点)

  • 在使用open函数打开一个FIFO(命名管道)时,如果以只写/读方式打开,并且没有其他进程以读/写模式打开相同的FIFO,则会发生阻塞。
  • 这是因为FIFO(命名管道)是基于进程间通信的一种机制,要求读和写操作成对出现。
    • 对于一个命名管道(FIFO),只有在读端和写端同时打开之后,open函数才会返回。
    • 也就是说,当一个进程以只读方式打开FIFO,而另一个进程以只写方式打开相同的FIFO时,两个进程都完成打开操作后,它们之前的阻塞状态将被解除。

解释:(重点)

  • 当你以只读方式打开FIFO时,如果没有其他进程以模式打开相同的FIFO,则取操作会一直等待
  • 同样地,当你以只写方式打开FIFO时,如果没有其他进程以模式打开相同的FIFO,则写入操作会一直等待
  • 只有在读端和写端都成功打开之后,两个进程之间的通信才能顺利进行。
  • 这种机制确保了读和写操作的同步,保证了数据的正确传输。
  • 因此,当你使用open函数打开一个FIFO时,要确保读端和写端都已经打开,才能结束等待并进行进一步的读写操作。
  • 如果是同一个key,第一个进程用shmget第三个参数是IPC_CREAT,第二个进程用shmget第三个参数是IPC_CREAT | IPC_EXCL,那么第二个进程会创建共享内存失败。
  • 如果第一个进程在创建共享内存时使用了shmget函数的第三个参数为IPC_CREAT,则表示如果共享内存不存在就创建一个新的。
  • 而第二个进程在创建共享内存时使用了IPC_CREAT | IPC_EXCL(即同时设置了IPC_CREAT和IPC_EXCL),表示如果共享内存已经存在,则返回错误。
  • 因此,在这种情况下,如果第一个进程已经创建了共享内存,那么第二个进程会因为IPC_EXCL的设置而无法再次创建共享内存,shmget函数会返回一个错误,第二个进程创建共享内存失败。
  • 这样可以确保同一个key只能被一个进程创建共享内存,防止重复创建和竞争条件的发生。

所以正因为有shmget函数第三个参数这样的机制,我们必要时要让参数是IPC_CREAT的进程等一下参数是IPC_CREAT | IPC_EXCL的进程,让后者先把共享内存创建好了,前者直接获取。

若是前者先创建共享内存,后者一旦判断同一个key值已经创建好了一块共享内存,就会返回错误。除非两个shmget函数第三个参数都是IPC_CREAT,这样无论哪个进程先创建共享内存,另一方都可以获取到对方创建的共享内存。

通过共享内存进程通信结果:

在这里插入图片描述
基于共享内存 + 管道的一个访问控制的效果:

  • 管道本身提供保护机制,我们自己也做了一次保护机制共享内存 + 管道的机制。
  • 如果这两个方案都不提供,裸的共享内存,被双方同时看到。
  • 我们两个进程在操作,就可能出现一些来回读写交叉的问题。
  • 导致数据不一致的问题,或者是访问控制方面的问题。

3. 相关概念

  • 临界资源: 被多个进程能够看到的资源叫做临界资源。
  • 如果没有对临界资源进行任何保护,对于临界资源的访问。
  • 双方进程在进行访问的时候,就都是乱序的。
  • 可能会因为读写交叉而导致的各种乱码、废弃数据、访问控制方面的问题!!
  • 临界资源有安全的也有不安全的,取决于内部是否做了保护。
  • 临界区: 对多个进程而言,访问临界资源的代码。
  • 我的进程代码中,有大量的代码,只有一部分代码,会访问临界资源。
  • 两个进程分别对共享资源做读写的代码叫做它们俩的临界区。
  • 原子性: 我们把一件事情,要不没做,要么做完了,叫原子性。
  • 没有中间状态。
  • 互斥: 任何时刻,只允许一个进程,访问临界资源。

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

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

相关文章

Python进行数据分析(详细教程)

1.为什么选择Python进行数据分析&#xff1f; Python是一门动态的、面向对象的脚本语言&#xff0c;同时也是一门简约&#xff0c;通俗易懂的编程语言。Python入门简单&#xff0c;代码可读性强&#xff0c;一段好的Python代码&#xff0c;阅读起来像是在读一篇外语文章。Pyth…

Jenkins搭建最简教程

纠结了一小会儿&#xff0c;到底要不要写这个&#xff0c;最终还是决定简单记录一下&#xff0c;因为Jenkins搭建实在是太简单了&#xff0c;虽然也有坑&#xff0c;但是坑主要在找稳定的版本上。 先学一个简称&#xff0c;LTS (Long Term Support) 属实是长见识了&#xff0c…

docker 搭建jenkins

1、拉取镜像 docker pull jenkins/jenkins:2.4162、创建文件夹 mkdir -p /home/jenkins_mount chmod 777 /home/jenkins_mount3、运行并构建容器 docker run --restartalways -d -p 10240:8080 -p 10241:50000 -v /home/jenkins_mount:/var/jenkins_home -v /etc/localtime:…

如何选择台式还是便携式多参数水质检测仪呢

选择台式还是便携式多参数水质检测仪主要取决于具体的使用需求和场景。 1.便携式多参数水质检测仪适用于需要在不同地点进行水质检测的情况&#xff0c;例如户外采样、实地调查等。它具有小巧轻便的特点&#xff0c;方便携带和操作&#xff0c;适合需要频繁移动或需要灵活使用的…

如何维护你的电脑:提升性能和延长使用寿命

如何维护你的电脑&#xff1a;提升性能和延长使用寿命 &#x1f607;博主简介&#xff1a;我是一名正在攻读研究生学位的人工智能专业学生&#xff0c;我可以为计算机、人工智能相关本科生和研究生提供排忧解惑的服务。如果您有任何问题或困惑&#xff0c;欢迎随时来交流哦&…

AWVS 15.6 使用教程

目录 介绍 版本 AWVS具有以下特点和功能&#xff1a; 功能介绍&#xff1a; Dashboard功能&#xff1a; Targets功能&#xff1a; Scans功能&#xff1a; Vulnerabilities功能&#xff1a; Reports功能&#xff1a; Users功能&#xff1a; Scan Profiles功能&#x…

2023 年牛客多校第四场题解

A Bobo String Construction 题意&#xff1a;给定一个 01 01 01 字符串 t t t&#xff0c;构造一个长度为 n n n 的 01 01 01 串 s s s&#xff0c;使得 t t t 在 c o n c a t ( t , s , t ) {\rm concat}(t, s, t) concat(t,s,t) 中仅出现两次。多测&#xff0c; 1 ≤…

【数据结构】实验十二:图 查找

实验十二 图查找 一、实验目的与要求 1&#xff09;掌握拓扑排序的应用&#xff1b; 2&#xff09;掌握查找的概念和算法&#xff1b; 3&#xff09;掌握查找的基本原理以及各种算法的实现&#xff1b; 4&#xff09;掌握查找的应用。 二、实验内容 1. 用邻接表建立一…

WIZnet W51000S-EVB-PICO 入门教程(一)

概述 W5100S-EVB-Pico是基于树莓派RP2040和全硬件TCP/IP协议栈控制器W5100S的微控制器开发板-基本上与树莓派Pico板相同&#xff0c;但通过W5100S芯片增加了以太网功能。 W5100S-EVB-Pico特点 RP2040规格参数 双核Arm Cortex-M0 133MHz264KB 高速SRAM和2MB板载内存通过…

JAVA基础-多线程入门(详解)

目录 引言 一&#xff0c;线程概念 二&#xff0c;创建线程 2.1&#xff0c;继承Thread类&#xff0c;重写run方法 2.2&#xff0c;实现Runnable接口&#xff0c;重写run方法&#xff0c;实现Runnable接口的实现类的实例对象作为Thread构造函 数的target 2.3&#xff0c;通…

RCU 使用及机制源码的一些分析

》内核新视界文章汇总《 文章目录 1 介绍2 使用方法2.1 经典 RCU2.2 不可抢占RCU2.3 加速版不可抢占RCU2.4 链表操作的RCU版本2.5 slab 缓存支持RCU 3 源码与实现机制的简单分析3.1 数据结构3.2 不可抢占RCU3.3 加速版不可抢占RCU3.4 可抢占RCU3.5 报告禁止状态3.6 宽限期的开…

教雅川学缠论03-分型

原著作者将K线走势分成四中类型&#xff0c;这四中类型&#xff0c;就叫做分型&#xff0c;注意&#xff0c;分型是K线的组合&#xff08;至少3个K线&#xff09;&#xff0c;如下 下面我们以2023年7月武汉控股日K示例 四个分型用图来表示的话&#xff0c;还是很简单的&…

spring5源码篇(13)——spring mvc无xml整合tomcat与父子容器的启动

spring-framework 版本&#xff1a;v5.3.19 文章目录 整合步骤实现原理ServletContainerInitializer与WebApplicationInitializer父容器的启动子容器的启动 相关面试题 整合步骤 试想这么一个场景。只用 spring mvc&#xff08;确切来说是spring-framework&#xff09;&#x…

Flink集群运行模式--Standalone运行模式

Flink集群运行模式--Standalone运行模式 一、实验目的二、实验内容三、实验原理四、实验环境五、实验步骤5.1 部署模式5.1.1 会话模式&#xff08;Session Mode&#xff09;5.1.2 单作业模式&#xff08;Per-Job Mode&#xff09;5.1.3 应用模式&#xff08;Application Mode&a…

从头开始:数据结构和算法入门(时间复杂度、空间复杂度)

目录 文章目录 前言 1.算法效率 1.1 如何衡量一个算法的好坏 1.2 算法的复杂度 2.时间复杂度 2.1 时间复杂度的概念 2.2 大O的渐进表示法 2.3常见时间复杂度计算 3.空间复杂度 4.常见复杂度对比 总结 前言 C语言的学习篇已经结束&#xff0c;今天开启新的篇章——数据结构和算…

gitee 配置ssh 公钥(私钥)

步骤1&#xff1a;添加/生成SSH公钥&#xff0c;码云提供了基于SSH协议的Git服务&#xff0c;在使用SSH协议访问项目仓库之前&#xff0c;需要先配置好账户/项目的SSH公钥。 绑定账户邮箱&#xff1a; git config --global user.name "Your Name" git config --glob…

【图像去噪】基于进化算法——自组织迁移算法(SOMA)的图像去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

HTML基础知识点总结

目录 1.HTML简介 2.HTML基础结构 主要字符&#xff1a; 3.基础知识 &#xff08;一&#xff09;p标签 &#xff08;二&#xff09;hr标签 &#xff08;三&#xff09;尖角号 &#xff08;四&#xff09;版权号 (五)div和span div span (六)列表 &#xff08;1&…

【Docker】Docker相关基础命令

目录 一、Docker服务相关命令 1、启动docker服务 2、停止docker服务 3、重启docker服务 4、查看docker服务状态 5、开机自启动docker服务 二、Images镜像相关命令 1、查看镜像 2、拉取镜像 3、搜索镜像 4、删除镜像 三、Container容器相关命令 1、创建容器 2、查…

pytorch实现梯度下降算法例子

如题&#xff0c;利用pytorch&#xff0c;通过代码实现机器学习中的梯度下降算法&#xff0c;求解如下方程&#xff1a; f ′ ( x , y ) x 2 20 y 2 {f}(x,y) x^2 20 y^2 f′(x,y)x220y2 的最小值。 Latex语法参考&#xff1a;https://blog.csdn.net/ViatorSun/article/d…