Liunx进程间通信

进程间通信

  • 进程间通信
    • 进程间通信的基本概念
    • 进程间通信的目的
  • 管道
    • 匿名管道
      • 进程池
    • 命名管道
  • system V进程间通信
    • system V进程间通信基本概念
      • system V共享内存
      • 共享内存和管道的对比
  • system V 信号量
    • 信号量
    • 同步和互斥

进程间通信

进程间通信的基本概念

进程间通信就是在不同进程之间传播或交换信息。

进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程

  • 资源共享:多个进程之间共享同样的资源。

  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程)。

  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态。
    进程通信的本质
    进程通信的本质就是让不同的进程看到同一个资源。
    在这里插入图片描述
    进程的通信是通过管道的,只能单向通信。

管道

我们在之前用过很多次管道,|现在我们在用一次,用管道链接两个不同的进程。
在这里插入图片描述

匿名管道

匿名管道是管道的一种,匿名管道是一个单向通信管道,是父子进程通信的管道。
在这里插入图片描述
pipe函数

int pipe(int pipefd[2]);

pipe函数可以为进程创建两个端口,一个读端口,一个是写端口。
创建管道流程

  1. 父进程打开双通道
    在这里插入图片描述
  2. 父进程创建子进程
    在这里插入图片描述
  3. 父进程和子进程关闭读写端
    在这里插入图片描述
    注意: 这里不能共用一个端口。读端和写端指向不同,不能用读端写入,写端读出。
    传输简单使用:
    首先介绍两个系统调用
    read和write
   ssize_t write (int fd,const void * buf,size_t count);

write( ) 会把参数buf所指的内存写入count个字节到参数fd所指的文件内。当然,文件读写位置也会随之移动。

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

接下来我们实际操作:以一个简单的模型
注意:必须严格按照fd[0]读取 fd[1]写入。否则就会出现下面这种情况。

#include<iostream>
#include <stdio.h>
#include <unistd.h>
#include <cstring>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include<vector>
using namespace std;int main()
{int fd[2]={0};  
//pipe创建两个端口 其中fd[0]为读端,fd[1]为写端
if(pipe(fd)<0)
{perror("worring");return 1;
}pid_t id=fork();
//子进程写入 父进程读出
if(id==0)
{//子进程关闭读端close(fd[1]);
string mes("I am son,hello..");
int con=10;
while(con)
{cout<<"doing"<<endl;write(fd[0],mes.c_str(),mes.size());con--;sleep(1);
}
exit(0);
}
close(fd[0]);
char buffer[64];
string x;
while(1)
{
size_t s=read(fd[1],buffer,sizeof(buffer));
if(s>0)
{cout<<"i am father"<<buffer<<endl;}
else if(s==0)
{cout<<"have done"<<endl;break;
}
else
{cout<<"worring"<<endl;break;
}
}return 0;
}

错误示范!
在这里插入图片描述
我们在接收的时候,开辟的buffer也是要注意,不能太小。
正确的实现代码:

#include<iostream>
#include <stdio.h>
#include <unistd.h>
#include <cstring>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include<vector>
using namespace std;int main()
{int fd[2]={0};  
//pipe创建两个端口 其中fd[0]为读端,fd[1]为写端
if(pipe(fd)<0)
{perror("worring");return 1;
}pid_t id=fork();
//子进程写入 父进程读出
if(id==0)
{//子进程关闭读端close(fd[0]);
string mes("I am son,hello..");
int con=2000;
while(con)
{cout<<"doing:"<<con<<endl;write(fd[1],mes.c_str(),mes.size());con--;}
exit(0);
}
close(fd[1]);
char buffer[17];
string x;
sleep(5);
while(1)
{
size_t s=read(fd[0],buffer,sizeof(buffer)-1);//读的尺寸小一个可以填充\0
if(s>0)
{buffer[s]='\0';
cout<<"i am father:"<<buffer<<"size:"<<s<<endl;}
else if(s==0)
{cout<<"have done"<<endl;break;
}
else
{cout<<"worring"<<endl;break;
}
}return 0;
}

管道的特点

  1. 写端进程不写,读端进程一直读,那么此时会因为管道里面没有数据可读,对应的读端进程会被挂起,直到管道里面有数据后,读端进程才会被唤醒。
  2. 读端进程不读,写端进程一直写,那么当管道被写满后,对应的写端进程会被挂起,直到管道当中的数据被读端进程读取后,写端进程才会被唤醒。
  3. 写端进程将数据写完后将写端关闭,那么读端进程将管道当中的数据读完后,就会继续执行该进程之后的代码逻辑,而不会被挂起。
  4. 读端进程将读端关闭,而写端进程还在一直向管道写入数据,那么操作系统会将写端进程杀掉。

测试管道大小

在这里插入图片描述
子进程一直写,父进程不读。则子进程会写满缓冲区。可以看到缓冲区大小是4096 4KB。
管道通信的过程
管道的4种情况

  1. 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
  2. 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)
  3. 写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾
  4. 读端关闭,写端一直写入,OS会直接杀掉写端进程,通过想目标进程发送SIGPIPE(13)信号,终止目标进程

进程池

接下来我们实现一个进程池。

命名管道

在上面中,我们理解了匿名管道,匿名管道是父子进程通信的管道。然而,不是父子又该如何呢。这时命名管道应运而生。命名管道实际上就是一个管道文件。他的注意事项:

  1. 普通文件是很难做到通信的,即便做到通信也无法解决一些安全问题。
  2. 命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。
    创建管道指令:mkfifo
    在这里插入图片描述
    此时我们像管道中写入:
while : ; do echo "hello fifo" ; sleep 1 ; done > fifo

在这里插入图片描述
cat重定向到我们的屏幕,就可以了。
创建一个命名管道
接下来我们用代码创建一个管道,并且让两个不同的进程进行通信。
在程序中创建命名管道使用mkfifo函数,mkfifo函数的函数原型如下:

int mkfifo(const char *pathname, mode_t mode);

mkfifo函数的第一个参数是pathname,表示要创建的命名管道文件。

  • 若pathname以路径的方式给出,则将命名管道文件创建在pathname路径下。
  • 若pathname以文件名的方式给出,则将命名管道文件默认创建在当前路径下。(注意当前路径的含义)
    mkfifo函数的第二个参数是mode,表示创建命名管道文件的默认权限。
    我们首选创建一个管道:
bool MakeFifo()// 创建管道
{int n = mkfifo(FILENAME, 0666);if(n < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return false;}std::cout << "mkfifo success... read" << std::endl;return true;
}

如果创建失败,则返回false。后面,则是吧管道当成一个普通文件,往里面读,往里面写就行了。就这么简单。

// server.cpp
#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include"comm.h"//#include "comm.h"using namespace std;bool MakeFifo()// 创建管道
{int n = mkfifo(FILENAME, 0666);if(n < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return false;}std::cout << "mkfifo success... read" << std::endl;return true;
}
int main()
{start:
int rfd = open(FILENAME, O_RDONLY);if(rfd < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;if(MakeFifo()){goto start;}else{ return 2;}}std::cout << "open fifo success..." << std::endl;char buffer[1024];//rfd是管道.fifo的fdwhile(1){size_t s=read(rfd,buffer,sizeof(buffer)-1);if(s>0){buffer[s]=0;cout<<"client say#"<<buffer<<endl;}   }close(rfd);return 0;
}

接下来是写端:

//client.cpp
#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include"comm.h"using namespace std;
int main()
{
int wfd=open(FILENAME,O_WRONLY);//写端口 wfd
if(wfd < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return 1;}string meassage; 
while(1)
{cout<<"please Enter#";getline(cin,meassage);size_t s=write(wfd,meassage.c_str(),meassage.size());//往管道的文件描述符wfd写。只需要吧管道当成文件就行了。
if(s<0)
{std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;break;
}}
return 0;
}

接下来,我们利用这一机制,进行进程间通信,将命令写入管道,然后提取命令。再利用execlp函数进行程序替换。

if(s>0){buffer[s]=0;if (fork() == 0){//childcout<<"client say#"<<buffer<<endl;execlp(buffer, buffer, NULL); //进程程序替换exit(1);}waitpid (-1, NULL, 0); //等待子进程}   

我们只需要加入这几句代码,让子进程帮我们进行代码置换。
命名管道和匿名管道的区别:

  1. 匿名管道由pipe函数创建并打开。
  2. 命名管道由mkfifo函数创建,由open函数打开。
  3. FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。
    那么在命令行当中的管道(“|”)到底是匿名管道还是命名管道呢?
    匿名管道喔。他们都是bash的子进程。

system V进程间通信

system V进程间通信基本概念

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

  1. system V共享内存
  2. system V消息队列
  3. system V信号量
    接下来,我们将详细谈论三种模式

system V共享内存

共享内存让不同进程看到同一份资源的方式就是,在物理内存当中申请一块内存空间,然后将这块内存空间分别与各个进程各自的页表之间建立映射,再在虚拟地址空间当中开辟空间并将虚拟地址填充到各自页表的对应位置,使得虚拟地址和物理地址之间建立起对应关系,至此这些进程便看到了同一份物理内存,这块物理内存就叫做共享内存。
在这里插入图片描述
进程在通信之时,在堆栈之间的共享内存开辟一块物理内存,该物理内存就是通信的场所。这个场所就是快递站,你把消息放快递站,我把快递拿出来。
共享内存的建立与释放
共享内存的建立大致包括以下两个过程:

  1. 在物理内存当中申请共享内存空间。

  2. 将申请到的共享内存挂接到地址空间,即建立映射关系。
    共享内存的释放大致包括以下两个过程:

  3. 将共享内存与地址空间去关联,即取消映射关系。

  4. 释放共享内存空间,即将物理内存归还给系统
    共享内存的创建
    创建共享内存我们需要用shmget函数,shmget函数的函数原型如下:

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

shmget函数的参数说明:

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

shmget调用成功,返回一个有效的共享内存标识符(用户层标识符)。
shmget调用失败,返回-1。
接下来我们介绍key值,key为了保证唯一性,所以有了系统函数!
传入shmget函数的第一个参数key,需要我们使用ftok函数进行获取

ftok函数的函数原型如下:

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

ftok函数的作用就是,将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,在使用shmget函数获取共享内存时,这个key值会被填充进维护共享内存的数据结构当中。
接下来我们用这两个函数产生一个共享内存接口!

#include<iostream>
#include<string>
#include<cstdlib>
#include<sys/ipc.h>
#include<sys/shm.h>
#include <sys/ipc.h>
#include <sys/shm.h>#include<sys/types.h>
#include<string.h>
using namespace std;
const string pathname="/home/zsc/lesson15";
const int proj_id=0x11223344;
const size_t size =4096;
key_t Getkey()
{key_t key=ftok(pathname.c_str(),proj_id);if(key < 0){std::cerr << "errno: " << errno << ", errstring: " <<  strerror(errno) << std::endl;exit(1);}
cout<<"key:"<<key<<endl;return key;}

我们将getkey封装ftok,运行完毕就可以获得一个key。

#include<iostream>#include"commond.hpp"int main()
{
//IPC_CREAT不存在就创建 存在就获取并返回
//IPC——CREAT|IPC_EXCL shm不存在就创建,存在就返回错误key_t key=Getkey();int shmid=shmget(key,size,IPC_CREAT|IPC_EXCL);if(shmid<0){std::cerr << "errno: " << errno << ", errstring: " <<  strerror(errno) << std::endl;return 1;}cout<<"shmid:"<<shmid<<endl;return 0;
}

此时,我们就创建了一个共享内存:通过指令

ipcs -m

在这里插入图片描述
此时我们就查询到了刚刚开辟的共享内存。
当我们关闭之后再次启动xshell发现,共享内存依然存在!
key和shmid的特点比较:
key:不在应用层使用,只用来在内核中标识shm的唯一性!
shmid:应用这个共享内存是,我们用shmid进行操作共享内存。
shmid共享内存的删除:
在这里插入图片描述

ipcrm -m shmid

共享内存的权限:
共享内存的权限可以在shmget中加上:
在这里插入图片描述
此时查看,便给共享内存获得了权限。perms就是权限
在这里插入图片描述
共享内存的挂载
共享内存的挂载就是将虚拟内存映射到物理内存。

在这里插入图片描述
这是用到的函数:shmat

char *s=(char*)shmat(shmid,nullptr,0);

如此我们便可以把内存挂载到系统分配的物理内存之中。
nattch:是检测多少个进程链接到共享内存上。
在这里插入图片描述
进程结束,则链接断开。
那么如何将shm从进程中移除呢?

shmdt

在这里插入图片描述
那我想彻底从OS删除怎么办呢?

 shmctl(shmid,IPC_RMID,nullptr);

所以共享内存的程序级别操作是以下流程:
在这里插入图片描述
接下来,我们进行进程通信
在这里插入图片描述
我们可以看到 写完了,但是另一边一直在读取。说明这种通信方式进程不是同步的。 删除线格式
共享内存特点:

  1. 共享内存的生命周期是随内核的!
  2. 共享内存不同步通信
  3. 共享内存是所有进程通信速度最快的

共享内存和管道的对比

共享内存的速度是进程通信最快的。当共享内存创建好后就不再需要调用系统接口进行通信了,而管道创建好后仍需要read、write等系统接口进行通信。实际上,共享内存是所有进程间通信方式中最快的一种通信方式。
我们先来看看管道通信:
在这里插入图片描述
从这张图可以看出,使用管道通信的方式,将一个文件从一个进程传输到另一个进程需要进行四次拷贝操作:

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

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

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

system V 信号量

信号量

信号量本质是一个计数器。
目的是让多个执行流看到同一个资源。

  1. 由于进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系叫做进程互斥。
  2. 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
    在进程中涉及到临界资源的程序段叫临界区。
  3. IPC资源必须删除,否则不会自动删除,因为system V IPC的生命周期随内核。

同步和互斥

在这里插入图片描述
特殊的信号量,二元信号量只有0和1,也就是互斥锁。完成互斥功能!
信号量:信号量表示对资源数目的计数器,每一个执行流访问公共资源内的一份资源,不应该让执行流直接访问,而是先申请信号资源,只要信号量减一成功,完成了对资源的预定机制。
如果申请不成功就挂起!
申请资源称为p操作,释放资源称为v操作!
p操作:申请资源 V操作:释放资源!
进程互斥
进程间通信通过共享资源来实现,这虽然解决了通信的问题,但是也引入了新的问题,那就是通信进程间共用的临界资源,若是不对临界资源进行保护,就可能产生各个进程从临界资源获取的数据不一致等问题。

保护临界资源的本质是保护临界区,我们把进程代码中访问临界资源的代码称之为临界区,信号量就是用来保护临界区的,信号量分为二元信号量和多元信号量。

比如当前有一块大小为100字节的资源,我们若是一25字节为一份,那么该资源可以被分为4份,那么此时这块资源可以由4个信号量进行标识。
在这里插入图片描述

system V IPC联系
通过对system V系列进程间通信的学习,可以发现共享内存、消息队列以及信号量,虽然它们内部的属性差别很大,但是维护它们的数据结构的第一个成员确实一样的,都是ipc_perm类型的成员变量。

这样设计的好处就是,在操作系统内可以定义一个struct ipc_perm类型的数组,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。
在这里插入图片描述

也就是说,在内核当中只需要将所有的IPC资源的ipc_perm成员组织成数组的样子,然后用切片的方式获取到该IPC资源的起始地址,然后就可以访问该IPC资源的每一个成员了

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

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

相关文章

6-LINUX-- C 程序的编译与调试

一.环境搭建 1.gcc的安装 1>.切换到管理员模式 sudo su ----> 输入密码 2>.apt install gcc //C语言的编译环境 3>.apt install g //c编译环境的搭建 4>.install update //软件升级 2.gcc分步编译链接 &#xff08;1&#xff09;预编译 gcc -E…

计算机生物科技在基因编辑中的应用及其前景

一、引言 基因编辑&#xff0c;作为一种能够精准修改生物体基因组的技术&#xff0c;近年来受到了广泛的关注。 而计算机生物科技作为连接计算机科学与生物学的桥梁&#xff0c;为基因编辑技术的快速发展提供了强大的支持。通过利用计算机算法和数据分析方法&#xff0c;研究人…

Dashe Media全球新闻稿发布协助您实现传播目标-海外媒体宣发

亚太区新闻稿发布网络 Dashe Media 是唯一一家于亚太区拥有专有记者网络和网上新闻媒体发布网络的全球新闻通讯社 Dashe Media 在该地区的 26 个国家拥有 200,000 名记者和编辑数据库&#xff0c;涵盖 500 个新闻类别、68,000 个新闻媒体和 1,500 个在线新闻媒体合作伙伴&…

合并两个 Git 仓库,保存所有提交记录

将两个 Git 代码库合并成一个同时保留修改记录的过程&#xff0c;可以通过几个步骤完成。 这里提供一种常见的方法&#xff0c;使用git remote和git merge命令来实现。 假设场景 代码库A&#xff1a;原始代码库&#xff0c;希望将另一个代码库合并到这个库中。代码库B&#…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Refresh)

可以进行页面下拉操作并显示刷新动效的容器组件。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 支持单个子组件。 从API version 11开始&#xff0c;Refresh子组件会跟随手势下拉而下移…

HarmonyOS 通知意图

之前的文章 我们讲了 harmonyos 中的 基础和进度条通知 那么 今天 我们来说说 任何给通知添加意图 通知意图 简单说 就是 当我们点击某个通知 如下图 然后 就会拉起某个 应用 就例如说 我们某个微信好友发消息给我们 我们 点击系统通知 可以直接跳到你们的聊天界面 好 回到…

JavaScript中的继承方式详细解析

什么是继承 继承是面向对象编程中的一个重要概念&#xff0c;它指的是一个对象&#xff08;或类&#xff09;可以获得另一个对象&#xff08;或类&#xff09;的属性和方法。在继承中&#xff0c;被继承的对象通常称为父类&#xff08;或基类、超类&#xff09;&#xff0c;继…

(css)vue 自定义背景 can‘t resolve

(css)vue 自定义背景 can’t resolve 旧写法&#xff1a; background-image: url(/assets/images/step-bg.jpg);background-size: 100% 100%; 新写法&#xff1a; background-image: url(~/assets/images/step-bg.jpg);background-size: 100% 100%; 解决参考&#xff1a;https…

深度学习PyTorch 之 transformer-中文多分类

transformer的原理部分在前面基本已经介绍完了&#xff0c;接下来就是代码部分&#xff0c;因为transformer可以做的任务有很多&#xff0c;文本的分类、时序预测、NER、文本生成、翻译等&#xff0c;其相关代码也会有些不同&#xff0c;所以会分别进行介绍 但是对于不同的任务…

【RabbitMQ | 第七篇】RabbitMQ实现JSON、Map格式数据的发送与接收

文章目录 7.RabbitMQ实现JSON、Map格式数据的发送与接收7.1消息发送端7.1.1引入依赖7.1.2yml配置7.1.3RabbitMQConfig配置类——&#xff08;非常重要&#xff09;&#xff08;1&#xff09;创建交换器方法&#xff08;2&#xff09;创建队列方法&#xff08;3&#xff09;绑定…

代码随想录算法训练营第27天|93.复原IP地址、78.子集、90.子集二

目录 一、力扣93.复原IP地址1.1 题目1.2 思路1.3 代码1.4 总结 二、力扣78.子集2.1 题目2.2 思路2.3 代码2.4 总结 三、力扣90.子集二3.1 题目3.2 思路3.3 代码3.4 总结 一、力扣93.复原IP地址 &#xff08;比较困难&#xff0c;做起来很吃力&#xff09; 1.1 题目 1.2 思路 …

【数据结构练习题】栈——1.括号匹配 2.逆波兰表达式求值 3.出栈入栈次序匹配 4.最小栈

♥♥♥♥♥个人主页♥♥♥♥♥ ♥♥♥♥♥数据结构练习题总结专栏♥♥♥♥♥ 文件目录 前言1.括号匹配1.1问题描述1.2解题思路1.3画图解释1.4代码实现2.逆波兰表达式求值 2.1问题描述2.2解题思路2.3画图解释2.4代码解释3.出栈入栈次序匹配 3.1问题描述3.2思路分析3.3画图解释3.…

【No.13】蓝桥杯二分查找|整数二分|实数二分|跳石头|M次方根|分巧克力(C++)

二分查找算法 知识点 二分查找原理讲解在单调递增序列 a 中查找 x 或 x 的后继在单调递增序列 a 中查找 x 或 x 的前驱 二分查找算法讲解 枚举查找即顺序查找&#xff0c; 实现原理是逐个比较数组 a[0:n-1] 中的元素&#xff0c;直到找到元素 x 或搜索整个数组后确定 x 不在…

CPU设计实战—异常处理指令

异常类型以及精确异常的处理 异常有点像中断&#xff0c;处理完还要回到原来的状态&#xff0c;所以需要对之前的状态进行保存。本CPU主要实现对以下异常的处理&#xff1a; 1.外部硬件中断 2.复位异常 3.系统调用异常&#xff08;发生在译码阶段&#xff09; 4.溢出异常&…

Linux下磁盘分区类型及文件系统扩容

本篇文章基础知识点较多&#xff0c;文章偏长。建议收藏~ 之前介绍过一篇文章 重新构建KVM虚拟机基础镜像&#xff0c;当中有个待优化的点。 Centos 官方的镜像中默认的系统盘(/dev/vda)的大小是8G空间 但是实际使用时&#xff0c;8G的系统盘肯定不满足需求。这个时候我们就需…

做好外贸网站SEO优化,拓展海外市场

随着全球贸易的发展和互联网的普及&#xff0c;越来越多的外贸企业将目光投向了网络&#xff0c;希望通过建立网站来拓展海外市场。然而&#xff0c;在竞争激烈的外贸市场中&#xff0c;要让自己的网站脱颖而出&#xff0c;吸引更多的目标客户&#xff0c;就需要进行有效的SEO优…

openGauss学习笔记-246 openGauss性能调优-SQL调优-经验总结:SQL语句改写规则

文章目录 openGauss学习笔记-246 openGauss性能调优-SQL调优-经验总结&#xff1a;SQL语句改写规则246.1 使用union all代替union246.2 join列增加非空过滤条件246.3 not in转not exists246.4 选择hashagg246.5 尝试将函数替换为case语句246.6 避免对索引使用函数或表达式运算2…

PyTorch学习笔记之基础函数篇(十三)

文章目录 7.7 torch.ceil() 函数7.8 torch.floor() 函数7.9 torch.clamp() 函数7.10 torch.neg() 函数7.11 torch.reciprocal() 函数7.12 torch.rsqrt() 函数7.13 torch.sqrt() 函数 7.7 torch.ceil() 函数 在PyTorch中&#xff0c;torch.ceil 函数用于对张量&#xff08;tens…

面试算法-50-二叉树的最大深度

题目 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3 解 class Solution {public int maxDepth(TreeNo…

《算法设计与分析第二版》100行 C语言实现 广度度优先算法 BFS——最短距离

抄录自课本P157页。 #include <stdio.h> #define MAXQ 100 // 队列大小 #define MAxN 10 // 最大迷宫大小 int n8; // 迷宫大小 char Maze [MAxN][MAxN] {{O,X,X,X,X,X,X,X,},{O,O,O,X,O,X,O,X,},{X,X,O,O,O,X,O,X,},{X,X,O,X,O,X,X,X,},…