System-V共享内存和基于管道通信实现的进程池
文章目录
- 一.进程间通信:
- 进程间通信的本质:
- 二.Linux管道通信
- 匿名管道:
- 关于管道通信的要点:
- 基于匿名管道构建进程池:
- 三.System-V共享内存
- 共享内存和命名管道协同通信
参考Linux内核源码版本------linux-2.4.3
一.进程间通信:
- 操作系统中,为了保证安全性,进程之间具有严格的独立性(独立的
PCB
,独立的虚拟地址空间mm_struct
和页表…等各种独立的系统资源),即便是父子进程之间也通过数据的写时拷贝保证了两者之间的的数据独立性.因此要实现进程间通信和任务协作,就要让不同进程的共同读写同一份信息资源.由于违背了进程独立性的原则,要实现进程间共享资源就需要一定的技术成本. - 进程间的独立性:
进程间通信的本质:
- 不同的进程对同一块内存资源进行的一系列的读写操作.
二.Linux管道通信
- 将同一个管道文件的文件结构体指针分别填入两个进程的文件信息列表(通过父子进程的继承关系或者
open
接口实现),之后两个进程便可以对管道文件的内核级读写缓冲区(本质上是一块内存)进行读写操作实现通信. - 管道通信是一种单向通信手段,有固定的读端进程和写端进程.
匿名管道:
- 匿名管道通信是父子进程间通信的一种方式.
- 匿名管道通信机制图解:
- 内核视角下管道文件结构体内部结构:
关于管道通信的要点:
- 管道通信可用于进程间协同,提供访问控制(同步与互斥):
- 管道读写端正常,如果管道中缓冲区为空,则读端进程进入阻塞状态
- 管道读写端正常,如果管道中缓冲区被写满,则写端进程进入阻塞状态
- 管道写端先关闭,管道读端
read
接口返回0,标识读取结束 - 管道读端先关闭,操作系统会终止写端进程.
基于匿名管道构建进程池:
Task.hpp
模拟任务列表
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>//重定义函数指针
typedef void (*task_t)();void task1()
{std::cout << "执行任务1:矩阵计算" << std::endl;
}
void task2()
{std::cout << "执行任务2:pid控制算法" << std::endl;
}
void task3()
{std::cout << "执行任务3:图像计算" << std::endl;
}
void task4()
{std::cout << "执行任务4:人脸识别算法" << std::endl;
}//向数组中加载任务
void LoadTask(std::vector<task_t> &tasks)
{tasks.push_back(task1);tasks.push_back(task2);tasks.push_back(task3);tasks.push_back(task4);
}
#include "Task.hpp"
using namespace std;
#define ChildNum 5//子进程信息结构体
class Channel
{
public: Channel(){}Channel(const string & Name,pid_t Childpid,int Pipefd): _Childpid(Childpid),_Pipefd(Pipefd),_Name(Name){}pid_t getpid() const {return _Childpid;}string PrintName() const {return _Name;}int Getfd() const {return _Pipefd;}
private:pid_t _Childpid;int _Pipefd; //保存管道的写入端string _Name;
};//子进程任务执行函数
void slaver(const vector<task_t>&Taskarr)
{//任务码int CommandCode = 0;while(true){int check = read(0,&CommandCode,sizeof(CommandCode));//若管道中没有数据且写入段没有关闭,子进程就会阻塞assert(check!=-1);if(check > 0){std::cout <<"slaver say@ get a command: "<< getpid() << " : CommandCode: " << CommandCode << std::endl;//子进程解析并执行命令if(CommandCode < 0 || CommandCode >= Taskarr.size()){cout << "CommandCode Error! slaver exit!" << endl;exit(0);}//子进程根据任务码执行任务Taskarr[CommandCode]();}else {//一旦父进程关闭管道写入端,check就会接收到0,子进程退出break;}}
}//父进程向子进程发送任务的接口
void ctrlSlaver(const std::vector<Channel> & channels,const vector<task_t>&Taskarr)
{int count = 10;while(count--){sleep(1);//随机选择子进程发送任务码int choseSlaver = rand()%channels.size();int Task = rand()%Taskarr.size();cout << "父进程向子进程" << channels[choseSlaver].PrintName() << "写入命令:" << Task << endl;write(channels[choseSlaver].Getfd(),&Task,sizeof(Task));}sleep(1);cout << "\n所有任务执行完毕,系统准备退出\n" << endl;sleep(2);
}//构建进程池接口
void InitProcessPool(vector<Channel>& ChildProc,const vector<task_t>&Taskarr)
{for(int i = 0; i < ChildNum; ++i){int pipefd[2];int check = pipe(pipefd);assert(!check); (void)check;pid_t pid = fork();assert(pid != -1);//父进程写 子进程读if(pid == 0){//子进程执行流close(pipefd[1]);//将stdin对应文件指针修改为管道的读入端dup2(pipefd[0],0);//将文件信息列表中对应的指针位置空close(pipefd[0]);slaver(Taskarr);close(0);exit(0);}close(pipefd[0]);//将管道的写入端存入channel对象中ChildProc.push_back(Channel(string("Process ") + to_string(pid),pid,pipefd[1])); }
}//父进程轮询等待子进程退出
void WaitChildProc(const std::vector<Channel> & channels)
{//先关闭各个管道的写入端,相应的子进程会自动退出for(auto& e : channels){close(e.Getfd());}//等待各个子进程退出for(auto & e : channels){int Status = 0;waitpid(e.getpid(),&Status,0);cout << "写入端关闭,子进程:" << e.getpid() << "退出,退出码:"<< WIFEXITED(Status) << endl;}
}int main()
{vector<task_t>Taskarr;LoadTask(Taskarr);srand(time(nullptr)^getpid()^1023);vector<Channel> ChildProc;InitProcessPool(ChildProc,Taskarr);ctrlSlaver(ChildProc,Taskarr);WaitChildProc(ChildProc);return 0;
}
- 命名管道和匿名管道的内核原理相同
三.System-V共享内存
- 共享内存通信原理:
- 构建共享内存通信环境的系统接口:
int shmget(key_t key, size_t size, int shmflg);
key
是用户自定义共享内存标识键,用ftok
接口获取size
是申请共享内存的大小shmflg
:取IPC_CREAT
时,接口可以申请共享内存并获取共享内存的key
,若参数指定的共享内存已存在则直接返回共享内存的key
;取IPC_CREAT | IPC_EXCL
时,接口只能用于申请新的共享内存.
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 接口作用:在当前进程的虚拟地址空间的共享区中为指定的共享内存块编址,并建立页表映射.
int shmdt(const void *shmaddr);
- 接口作用:共享内存块与当前进程的虚拟地址空间取消关联,进程将无法再访问指定的共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 接口作用:对共享内存块进行
cmd
码指定的控制操作(比如释放操作),也可以用于获取共享内存块在内核中的描述信息
- 接口作用:对共享内存块进行
- 共享内存通信环境中,由于多个进程可以对同一个内存块直接进行读写操作,因此,共享内存通信缺少同步互斥机制,无法保证数据的读写安全,为此,可以借助命名管道为共享内存通信提供读写控制.
共享内存和命名管道协同通信
- 构建通信环境的接口头文件:
#ifndef __COMM_HPP__
#define __COMM_HPP__#include "log.hpp"
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>using namespace std;
const int SIZE = 4096;
const string pathname = "/home/user1/LinuxLearning/sharedMEM";
const int pro_id = 0x123456;log LOG;//获取自定义共享内存key
key_t GetKey(const string pathname,const int pro_id)
{//KEY生成器key_t K = ftok(pathname.c_str(),pro_id);if(K < 0){LOG(Fatal,"GetKey Error, message: %s\n",strerror(errno));exit(-1);}LOG(Info,"key generated, message: %s\n",strerror(errno));return K;
}//调用系统接口申请共享内存
int GetShareMemHelper(int flag)
{key_t KEY = GetKey(pathname,pro_id);//系统调用接口shmget申请共享内存或返回已存在的共享内存idint shmid = shmget(KEY,SIZE,flag);if(shmid == -1){LOG(Fatal,"Get ShareMem failed, message: %s\n",strerror(errno));exit(-1);}LOG(Info,"Get ShareMem completed, message: %s\n",strerror(errno));return shmid;
}//申请新的共享内存
int CreateShm()
{return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}//获取已存在的共享内存的id
int GetShm()
{return GetShareMemHelper(IPC_CREAT);
}#define FIFO_FILE "./myfifo"
#define MODE 0664enum
{FIFO_CREATE_ERR = 1,FIFO_DELETE_ERR,FIFO_OPEN_ERR
};class Init
{
public:Init(){// 创建管道int n = mkfifo(FIFO_FILE, MODE);if (n == -1){perror("mkfifo");exit(FIFO_CREATE_ERR);}}~Init(){//管道去链接,若引用计数为0则删除管道文件int m = unlink(FIFO_FILE);if (m == -1){perror("unlink");exit(FIFO_DELETE_ERR);}}
};#endif
- 自制日志类log:
#pragma once
#include <time.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <stdlib.h>//日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
//日志写入方式
#define Screen 1
#define Onefile 2
#define Classfile 3
#define LogFile "log.txt"using std :: string;
class log
{
public:log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}string LeveltoString(int level){switch (level){case 0:return string("Info");break;case 1:return string("Debug");break;case 2:return string("Warning");break;case 3:return string("Error");break;case 4:return string("Fatal");break;default:break;}}void operator()(int level,char * format,...){//将时间格式化存入tm结构体中time_t t = time(nullptr);struct tm* ctime = localtime(&t);char leftbuffer[1024];snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d %d:%d:%d]",LeveltoString(level).c_str(),ctime->tm_year+1900,ctime->tm_mon+1,ctime->tm_mday,ctime->tm_hour,ctime->tm_min,ctime->tm_sec);//解析可变参数va_list vls;va_start(vls,format);char rightbuffer[1024];vsnprintf(rightbuffer,sizeof(rightbuffer),format,vls);va_end(vls);//合并时间和可变参数char logtxt[2048];snprintf(logtxt,sizeof(logtxt),"%s %s\n",leftbuffer,rightbuffer);//执行日志记录printLog(level,string(logtxt));}//日志信息写出接口void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen://将日志信息打印到标准输出std::cout << logtxt << std::endl;break;case Onefile://将日志信息存入log.txtprintOneFile(LogFile, logtxt);break;case Classfile://将日志信息存入指定的分类日志文件printClassFile(level, logtxt);break;default:break;}}//日志信息写到log.txt中void printOneFile(const std::string &logname, const std::string &logtxt){//path-->日志保存路径 logname-->日志文件名std::string _logname = path + logname;//打开"log.txt"日志文件int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}//日志信息写到log.txt.level中void printClassFile(int level, const std::string &logtxt){//对日志文件名进行修改,根据日志等级分出多个日志文件std::string filename = LogFile;filename += ".";// "log.txt.Debug(Warning)(Fatal)"filename += LeveltoString(level); printOneFile(filename, logtxt);}~log(){}
private:int printMethod;std::string path;
};
- 读端进程示例:
#include "log.hpp"
#include "ShareMemBuild.hpp"extern log LOG;int main()
{//创建管道和共享内存Init pipeCreate;int shmid = CreateShm();//建立共享内存与进程虚拟地址空间之间的映射,并获取共享内存的虚拟地址char * shmaddr = (char *)shmat(shmid,NULL,0);//打开管道文件int fd = open(FIFO_FILE,O_RDONLY);if(fd == -1){LOG(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}while(true){//借助管道进行共享内存的读写控制,若写端没有给信号,则读端保持阻塞状态char c;int RSize = read(fd,&c,sizeof(c));if(RSize <=0) break;//直接访问共享内存,实现高效通信cout << "client say@ " << shmaddr << endl; sleep(1);}//进程与共享内存断开连接shmdt(shmaddr);//将共享内存标记为已销毁shmctl(shmid,IPC_RMID,nullptr);close(fd);return 0;
}
- 写端进程示例:
#include "log.hpp"
#include "ShareMemBuild.hpp"extern log LOG;int main()
{ //获取共享内存标识int shmid = GetShm();//建立共享内存与进程虚拟地址空间之间的映射,并获取共享内存的虚拟地址char * shmaddr = (char *)shmat(shmid,NULL,0);//打开管道文件int fd = open(FIFO_FILE,O_WRONLY);if(fd == -1){LOG(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}while(true){cout << "Please Enter@ ";//将信息写入共享内存fgets(shmaddr, 4096, stdin);//管道写入信号,解除读端的阻塞状态write(fd, "c", 1); }//进程与共享内存断开连接shmdt(shmaddr);close(fd);return 0;
}
- 多个进程直接通过各自的虚拟地址空间对同一个内存块进行访问使得共享内存通信具有很高的通信效率.管道通信过程中,数据至少要经过两次拷贝(用户读写缓冲区和内核读写缓冲区之间的拷贝),而共享内存通信不存在通信数据拷贝问题
- 共享内存,消息队列,信号量等通信内存资源(称为
ipc
资源)统一由操作系统描述为各种数据结构统一进行管理,在Linux内核中,描述共享内存,消息队列,信号量的结构体形成继承体系:(C语言实现的继承体系)
相关文章:

System-V共享内存和基于管道通信实现的进程池
文章目录 一.进程间通信:进程间通信的本质: 二.Linux管道通信匿名管道:关于管道通信的要点:基于匿名管道构建进程池: 三.System-V共享内存共享内存和命名管道协同通信 参考Linux内核源码版本------linux-2.4.3 一.进程间通信: 操作系统中,为了保证安全性,进程之间具有严格的独…...

Python武器库开发-前端篇之CSS基本语法(三十)
前端篇之CSS基本语法(三十) CSS简介 CSS(层叠样式表)是一种用于描述网页外观和布局的样式表语言。它与 HTML 一起,帮助开发者对网页进行美化和布局。CSS通过定义网页元素的颜色、字体、大小、背景、边框等属性,使网页变得更加美…...

微信小程序实现类似Vue中的computed、watch功能
微信小程序实现类似Vue中的computed、watch功能 构建npm使用 构建npm 创建包管理器 进入小程序后,打开终端,点击顶部“视图” - “终端” 新建终端 使用 npm init -y初始化包管理器,生成一个package.json文件 安装 npm 包 npm install --…...
[JVM] 美团二面,说一下JVM数据区域
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。这些区域有不同的用途。 文章目录 线程私有的数据区域1. 程序计数器2. Java 虚拟机栈3. 本地方法栈 线程共享的数据区域1. Java 堆2. 方法区3. 运行时常量池4. 直接内存 线程私有的数据区域 …...
【React】useReducer
让 React 管理多个相对关联的状态数据 import { useReducer } from "react" // 1. 定义reducer函数,根据不同的action返回不同的状态 function reducer(state, action) {switch (action.type) {case ADD:return state action.payloadcase SUB:return st…...

leetcode刷题详解二
160. 相交链表 本质上是走过自己的路,再走过对方的路,这是求两个链表相交的方法 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {//本质上是走过自己的路,再走过对方的路if(headA NULL|| headB NULL){return NULL;}Lis…...
利用opencv/暗通道方法检测图像是否有雾-利用opencv/暗通道方法对深度学习目标检测算法结果进行二次识别提高准确率
目录 1 Python版本 2 C++版本 本来利用yolov5检测浓雾的,但是发现yolov5的检测结果会把一些正常天气检测成雾天,这种时候其实可以通过增加正常类,也就是将正常天气被误检成浓雾的图片当成一个正常类别去训练,但是不想标注图片,也不想重新训练算法了,因此想是不是可以用…...

Linux | 重定向 | 文件概念 | 查看文件 | 查看时间 | 查找文件 | zip
Linux | 重定向 | 文件概念 | 查看文件 | 查看时间 | 查找文件 | zip 文章目录 Linux | 重定向 | 文件概念 | 查看文件 | 查看时间 | 查找文件 | zip一、more1.1 输出重定向>和>>1.2 输入重定向< 二、 再谈一切皆文件三、less指令【重要】四、head指令五、tail指令…...

【广州华锐互动】利用VR体验环保低碳生活能带来哪些教育意义?
随着科技的不断发展,虚拟现实(VR)技术已经逐渐走进了我们的生活。从游戏娱乐到教育培训,VR技术的应用范围越来越广泛。而在这个追求绿色、环保的时代,VR技术也为我们带来了一种全新的环保低碳生活方式。让我们一起走进…...

python爬虫中 HTTP 到 HTTPS 的自动转换
前言 在当今互联网世界中,随着网络安全的重要性日益增加,越来越多的网站采用了 HTTPS 协议来保护用户数据的安全。然而,许多网站仍然支持 HTTP 协议,这就给我们的网络爬虫项目带来了一些挑战。为了应对这种情况,我们需…...

卷积神经网络(CNN)识别验证码
文章目录 一、前言二、前期工作1. 设置GPU(如果使用的是CPU可以忽略这步)2. 导入数据3. 查看数据4.标签数字化 二、构建一个tf.data.Dataset1.预处理函数2.加载数据3.配置数据 三、搭建网络模型四、编译五、训练六、模型评估七、保存和加载模型八、预测 …...

使用 PyODPS 采集神策事件数据
文章目录 一、前言二、数据采集、处理和入库2.1 获取神策 token2.2 请求神策数据2.3 数据处理-面向数组2.4 测试阿里云 DataFrame 入库2.5 调度设计与配置2.6 项目代码整合 三、小结四、花絮-避坑指南第一坑:阿里云仅深圳节点支持神策数据第二坑:神策 To…...

罗技M590鼠标usb优联连接不上
手里有一个罗技M590鼠标从18年4月一直用到现在,质量很好,除了滚轮有些松别的没毛病。最近一台笔记本电脑办公不太够用,又领了一个台式机,就想到M590支持双模连接,并且支持Flow,就把usb优联接收器从电池仓拿…...
天池 机器学习算法(一): 基于逻辑回归的分类预测
pytorch实战 课时7 神经网络 MSE的缺点:偏导值在输出概率值接近0或者接近1的时候非常小,这可能会造成模型刚开始训练时,偏导值几乎消失,模型速度非常慢。 交叉熵损失函数:平方损失则过于严格,需要使用更合…...

45岁后,3部位“越干净”,往往身体越健康,占一个也要恭喜!
众所周知,人的生命有长有短,而我们的身体健康状态,也同样会受到年龄的影响,就身体的年龄层次而言,往往需要我们用身体内部的干净程度来维持,换句话说就是:若是你的身体内部越干净,那…...

Windows安装Hadoop运行环境
1、下载Hadoop 2、解压Hadoop tar zxvf hadoop-3.1.1.tar.gz3、设置Hadoop环境变量 3.1.1、系统环境变量 # HADOOP_HOME D:\software\hadoop-3.1.13.1.2、Path 环境变量 %HADOOP_HOME%\bin %HADOOP_HOME%\sbin3.1.3、修改Hadoop文件JAVA_HOME 注 : 路径中不要出现空格 ,…...

软件测试 | MySQL 主键约束详解:保障数据完整性与性能优化
📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢交流讨论:欢迎加入我们一起学习!📢资源分享:耗时200小时精选的「软件测试」资…...
深入了解Linux中的scp命令及高级用法
Linux操作系统中,scp(Secure Copy Protocol)命令是一个用于在本地系统和远程系统之间安全复制文件的强大工具。通过基于SSH的加密通信,scp提供了安全的文件传输方式。在本文中,我们将深入介绍scp命令的基本语法以及一些…...
moviepy 视频剪切,拼接,音频处理
官网 使用matplotlib — moviepy-cn 文档 案例 from moviepy.editor import * from moviepy.video.fx import resize from PIL import Imagefile1r"D:\xy_fs_try\video_to_deal\spider_video\file\vedeo3.mp4" file2r"D:\xy_fs_try\video_to_deal\spider_video\…...

ubuntu搭建phpmyadmin+wordpress
Ubuntu搭建phpmyadmin wordpress Linux系统设置:Ubuntu 22配置apache2搭建phpmyadmin配置Nginx环境,搭建wordpress Linux系统设置:Ubuntu 22 配置apache2 安装apache2 sudo apt -y install apache2设置端口号为8080 sudo vim /etc/apache…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...

听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...