-
我喜爱一切不彻底的事物。琥珀里的时间,微暗的火,一生都在半途而废,一生都怀抱热望。夹竹桃掉落在青草上,是刚刚醒来的风车;静止多年的水,轻轻晃动成冰。我喜爱你忽然捂住我喋喋不休的口,教我沉默。——张定浩前言 时光如流水,来去匆匆,有的时候你并不知道你是虚拟的环境中还是在真实的环境中,一切只是内核kernel的感受而已。。。 一种不同的体验,几种不同的实现方式,那么。。。你又在娜里。。。虚拟化的分类 虚拟化场景其实和现实一样一样的。。。。 在虚拟化的场景中,虚拟机有的时候知道自己运行在虚拟化环境之中,那么这种就需要修改内核,从而也就是1型虚拟化,例如vmware ESX和ESXI,直接由hypervisor来管理硬件,从而提供给上层的虚拟机使用,这个时候,虚拟机使用的内核调用方式就是hyper call,而不是常规的system call了,这种相对于其他的虚拟化技术,效率高了不少(虚拟机必须修改内核才能发起hypervisor call)。 在二型虚拟化中,也就是硬件上面运行的是一个宿主机,然后在其中运行了一个用户空间的程序,用来提供上层虚拟机的管理,这种可以称之为VMM(virtual machine monitor)或者hypervisor,在虚拟机进行调用相关指令的时候,都是发送请求到宿主机,然后被vmm捕获到,然后再进行运行或者调用外部的硬件指令。这种由于指令都要进行最少一次的转换,从而效率损失不少(虚拟机可以运行各种操作系统)。 在现实中,我们进公司的时候,到底是选择外包呢,还是不选外包呢,在使用外包的时候,其实就是第一种类型的虚拟化了,所有人都明确的知道自己在外包之中,从而直接调用外包公司的接口提供服务,这种效率可能很高,但是硬件环境肯定是差的,从而。。。。我也不知道到底是好不好,哈哈、。。。。 在不选择外包的时候,每个人都生活在一个虚拟的空间之中,以为自己使用的是所有的CPU资源,以为自己使用的是所有的物理内存,以为自己能驱动所有的硬件,其实。。。。到底有多少资源能够使用,这个也是不确定的,,,没准,也只是生活在一种虚拟化的场景之中。。。梦中梦。。。内存的虚拟化 在虚拟化场景中,CPU和内存其实是不可分割的,基本上使用的是哪个物理机的CPU,那么就必然会使用其内存,为啥呢? CPU的硬件中,实现了两种主要的芯片,一个是MMU,内存管理单元,主要是用来管理内存的,其实也就是从线性地址转换到物理地址,在进程中,使用的线性地址,也就是每个进程能看到的内存地址其实是整个的物理地址,其中划分了一部分为内核使用,剩余的都是进程使用的内存空间,而每次在进程使用变量的时候,那么这些变量值都是保存在内存之中,那么就要将这个地址在CPU中去找,然后就经过MMU,找到物理地址,而CPU的速度远远大于内存的速度,那么为了匹配这种速度,从而也就有了另外一种芯片,也就是TLB,主要就是用来缓存线性地址到物理地址转换的物理结果,从而每次在寻找地址的时候,都是先找TLB,如果没有那么就会经过MMU转换,然后找到物理内存。 在CPU的物理结构中,有一级缓存,有二级缓存,有三级缓存,三级缓存一般是各个CPU共享的,从而有可能发生资源占用,从而也就从SMP的架构转换到了NUMA架构,从而也就是非一致性内存访问。主要的目的也就是为了提高性能,那么在使用CPU的时候,不可能去别的物理机上找到内存地址,然后读取数据,从而为了提高速度和性能,也就是CPU和MEM基本上是绑定的,在同一个物理机上使用。 超卖的概念,CPU是虚拟的,内存也是虚拟的,其实VCPU也就是虚拟CPU,其实也就是物理机上的一个进程,其实你虚拟的cpu个数可以超过物理的核心数,但是一般超卖的比是1.5,看性能而定,并且要看服务器的压力,也就是CPU的平均负载。内存也是虚拟的,而且有的类库是每个进程或者每个虚拟机都会使用的,从而也可以超过物理内存,但是。。。好像没有一个比例。 在模拟的场景中,一般的步骤就是虚拟机的的虚拟内存转换成虚拟机的物理内存,然后转换成宿主机的虚拟内存,然后找到真正的物理内存,需要经过两次转换,而且存在多个虚拟机的时候,每个虚拟机的TLB基本上都不能命中,从而导致性能大大降低。 从而在硬件层面,提供了硬件辅助的虚拟化技术,例如tagged TLB,也就是对TLB加了一个字段,表示为是哪个虚拟机的线性地址到物理地址的转换;例如提供了MMU的内存单元管理功能,也就是直接将虚拟地址换到物理机的虚拟地址,从而省略了转换到虚拟机的物理地址的步骤,从而能大大的提高性能。 硬支持的内存虚拟化(AMD Nested Page Tables[NPT]和Intel Extended Page Tables[EPT]) I/O的虚拟化 I/O设备,一般分为驱动器和设备本身,而驱动程序运行在内核中,在虚拟话的场景中,如果进程需要调度外部的IO设备,那么首先进程会调用虚拟机的驱动程序,驱动虚拟机的硬件,然后被物理机的vmm捕获,然后将调用物理机的驱动程序,然后才真正的调用物理设备。也就是也分为两步走。 在IO设备的虚拟化场景中,一种是使用模拟的方式,也就是通过软件的模拟,在linux中,一切皆文件,从而也就是提供了一堆的文件进行调用。 一种是使用半虚拟化的方式,这种也是需要硬件的支持的,也就是虚拟机驱动程序直接调用的是物理机的驱动程序,然后驱动硬件,从而省略了虚拟机的驱动调用虚拟机的设备步骤。 最后一种就是IO透传技术,其主要使用的方式就是直接将一些网卡设备或者硬盘设备直接绑定给虚拟机使用,从而基本上达到物理硬件的性能,不过这些硬件的管理还是需要使用hypervisor来进行管理。 硬件支持的设备和I/O虚拟化(Intel VT-d,AMD IOMMU)总结 虚拟化的出现不过是为了更好的利用物理主机的资源,例如CPU,内存,I/O。
-
客户端: 功能: 连接:获取ip地址和端口号,并连接 取消:关闭窗口 工程文件加上 QT=+=core gui network 相关类与函数 QTcpSocket类:(需要被包含) connectToHost() 连接。参数:QHostAdress地址对象(可用QString初始化),端口号(16位短整型)(QString自带进制转换,如ip.toshort) 连接成功后会发出一个信号:&QTcpSocket::connected 连接异常也会发出一个信号:&QTcpSocket::disconnected peerAddress();获取客户端地址 peerAddress的子函数toString():将其转化位字符串 peerPort():获取客户端端口号(并非服务器端口) write():参数:QByteArray ,可用append拼接,发送后会出现&QTcpSocket::readyread信号 QHostAddress类:(需要被包含) 其他函数: (QTcpSocket * )sender();获取信号的发送者并强制转为QTcpSocket指针 服务器: 相关类与函数: QTcpServer类: listen():监听网卡,参数QHostAdress::AnyIPv4,端口号 如果有信号,会发出QTcpServer::newConnection信号 nextPendingConnection() 建立TCP连接,返回QTcpSocket类型 实现服务器与客户端连接 在widget构造函数中监听网卡ip,等待请求,收到信号后与客户端建立连接 注意,必须使用127开头的地址 127.0.0.1 地址称为本地回环地址,是一种特殊的网络地址,用于让单独的计算机进行自我回路测试和通信。 这个地址在 IP 协议中被定义为环回地址。 在网络设备中,网络接口上的 127.0.0.1 地址本质上是本机对自己的网络地址。 通信 打开其他窗口:新建qt->设计师...,在客户端创建新的窗口类chat this->hide()隐藏窗口 创建新窗口对象时要用堆空间创建,也即是指针形式,这样申请的内存不会被销毁, widget在连接成功后隐藏当前窗口,并打开新窗口chat,与打开widget窗口类似 chat *k=new chat(SOCKET); k->show(); 要将socket信息传到新窗口中,所以类chat中要有参数,并在chat构造函数中初始化 QTcpSocket *s 在新窗口中,使用socket中的write发送信息,参数是QByteArray类型 在服务器中连接后再connect一个新的信号与曹: &QTcpSocket::readyRead &Widget::data_resslot 在槽函数中要注意变量的范围,如果QTcpSocket指针是内部变量,则需要使用 (QTcpSocket*)sender(); 将其转化为QTcpSocket类型指针,再用readAll读取所有发送的数据。 服务器代码: chat.h #ifndef CHAT_H #define CHAT_H #include <QWidget> #include <QTcpSocket> namespace Ui { class chat; } class chat : public QWidget { Q_OBJECT public: explicit chat(QTcpSocket *s,QWidget *parent = nullptr); ~chat(); private slots: void on_clearButton_clicked(); void on_sendButton_2_clicked(); private: Ui::chat *ui; QTcpSocket *SOCKET; }; #endif // CHAT_H widget.h #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpSocket> #include <QHostAddress> #include <QDebug> #include <QMessageBox> #include <chat.h> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_cancelButton_2_clicked(); void on_connectButton_clicked(); void on_pushButton_clicked(); private: Ui::Widget *ui; QTcpSocket *SOCKET; }; #endif // WIDGET_H chat.cpp #include "chat.h" #include "ui_chat.h" chat::chat(QTcpSocket *s,QWidget *parent) : QWidget(parent), ui(new Ui::chat) { ui->setupUi(this); SOCKET=s; } chat::~chat() { delete ui; } void chat::on_clearButton_clicked() { ui->lineEdit->clear(); } void chat::on_sendButton_2_clicked() { QString text1=ui->lineEdit->text(); QByteArray ba; ba.append(text1); //write只能发送QByteArray类型数据 SOCKET->write(ba); } widget.cpp #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); SOCKET=new QTcpSocket; //成功/异常警告 connect(SOCKET,&QTcpSocket::connected,[this]() { QMessageBox::information(this,"提示","连接成功"); this->hide(); chat *k=new chat(SOCKET); k->show(); }); connect(SOCKET,&QTcpSocket::disconnected,[this]() { QMessageBox::warning(this,"警告","连接异常"); }); } Widget::~Widget() { delete ui; } void Widget::on_cancelButton_2_clicked() { this->close(); } void Widget::on_connectButton_clicked() { //获取ip与端口号 QString com1=ui->COMlineEdit_2->text(); QString ip1=ui->IPlineEdit->text(); if(com1.isEmpty()||ip1.isEmpty()) { QMessageBox::warning(this,"警告","请输入ip地址和端口号"); } //开始连接 SOCKET->abort(); SOCKET->connectToHost(QHostAddress(ip1),com1.toShort()); } void Widget::on_pushButton_clicked() { qDebug()<<"disconnect"; SOCKET->disconnectFromHost(); } 服务器代码: widget.h #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QDebug> #include <QTcpServer> #include <QTcpSocket> #include <QHostAddress> #include <QMessageBox> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE #define TCPCOM 8000 class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void serverslot(); void on_check_clicked(); void data_resslot(); private: Ui::Widget *ui; QTcpServer *SERVER; }; #endif // WIDGET_H widget.cpp #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); SERVER=new QTcpServer; SERVER->listen(QHostAddress::Any,TCPCOM); qDebug()<<TCPCOM; connect(SERVER,&QTcpServer::newConnection,this,&Widget::serverslot); } Widget::~Widget() { delete ui; } void Widget::serverslot() { QTcpSocket *SOCKET1; SOCKET1=SERVER->nextPendingConnection(); QHostAddress ip1=SOCKET1->peerAddress(); quint16 com1=SOCKET1->peerPort(); ui->comlineEdit->setText(QString::number(com1)); ui->iplineEdit_2->setText(ip1.toString()); QMessageBox::information(this,"提示","连接成功"); connect(SOCKET1,&QTcpSocket::readyRead,this,&Widget::data_resslot); } void Widget::on_check_clicked() { if(SERVER->isListening()) { qDebug()<<"IS LISTENING"; } } void Widget::data_resslot() { QTcpSocket *Socket=(QTcpSocket*)sender(); ui->datalineEdit->setText(Socket->readAll()); } 结果: 多线程 在服务器中启用多线程,即可接收多个客户端的连接。 相关类与函数 在文件中添加c++类,在继承中继承QThread,添加后多了.h和.cpp文件 QThread类,需要包含 start()打开线程 虚函数run():在run里连接信号与槽,线程处理函数 自定义信号 在类中定义,和private同级,在信号传递时可以附加参数 signals: void sendtowidget(QByteArray ba); 在需要发出信号的地方加上即可,最后在widget中连接信号与槽 QByteArray ba=socket->readall(); emit sendtowidget(ba); ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/qq_59612601/article/details/139992170
-
在数字化时代,互联网已经成为我们日常生活和工作中不可或缺的一部分。无论是家庭网络还是企业办公环境,每台接入网络的设备都需要一个独特的标识来区分彼此,这个标识就是IP地址。IP地址全称为“互联网协议地址”,是设备在网络中的唯一身份标识。那么,什么是内网IP地址?如何查询电脑内网IP地址?下面跟着虎观代理小二一起来了解一下吧。 原图请到原文查看 一、内网IP地址概述 什么是内网IP地址?内网IP地址是指在局域网内部使用的IP地址,通常由路由器或局域网服务器分配。与全球唯一的外网IP地址不同,内网IP地址只在局域网内部有效,不同局域网中的设备可以拥有相同的内网IP地址而不会产生冲突。内网IP地址的范围通常在192.168.0.0至192.168.255.255之间,这是由IPv4地址的私有地址段决定的。 二、内网IP地址的作用 内网IP地址在局域网内部扮演着重要的角色。它使得局域网内的设备能够相互识别并进行通信,无论是文件共享、打印机访问还是局域网游戏等应用,都离不开内网IP地址的支持。同时,内网IP地址还提供了网络安全的基础,通过合理的IP地址规划和管理,可以实现对网络流量的监控和控制,提高网络的安全性。 三、如何查询电脑内网IP地址 查询电脑内网IP地址有多种方法,以下是几种常用的方法: 1、通过命令提示符(CMD)查询 按下【Win+R】键打开运行对话框,输入【cmd】并按回车,打开命令提示符窗口。 在命令提示符窗口中,输入【ipconfig】并按回车。 在显示的信息中,找到IPv4 地址一项,其后的数字即为电脑的内网IP地址。 2、通过网络连接设置查询 点击任务栏右下角的网络图标,在弹出的菜单中选择【网络和Internet设置】。 在设置窗口中,点击【状态】,向下滚动并点击【查看硬件和连接属性】。 在新窗口中,找到正在使用的网络适配器,并查看“IPv4 地址”。 3、使用PowerShell命令查询 在搜索栏中输入【PowerShell】并打开它。 输入【Get-NetIPAddress】命令并按回车。 在返回的信息中,查找“IPv4Address”属性,这将显示您的内网IP地址。 4、通过控制面板查询 按下【Win+R】键打开运行对话框,输入【control】并按回车,打开控制面板窗口。 在控制面板中,选择【网络和Internet】,然后点击【网络和共享中心】。 点击当前连接的网络名称,选择【详细信息】。 在网络连接详细信息窗口中,找到“IPv4地址”字段。 结尾: 通过本文的介绍,相信读者已经对内网IP地址有了更深入的了解。内网IP地址作为局域网内部设备的唯一标识,对于网络的正常运行和管理至关重要。掌握查询内网IP地址的方法,不仅可以帮助我们更好地理解和使用网络环境,还能在出现问题时迅速定位并解决。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/hgdlip/article/details/140762911
-
在 Ubuntu 中,如果遇到可以联网但是无法打开许多网页的问题,这可能是 DNS 设置不正确或者网络配置有误引起的。重置网络配置到默认设置可以帮助解决这类问题。以下是几种方法来重置 Ubuntu 的网络配置: ### 1. 重启网络服务 有时候简单地重启网络服务就能解决问题: ```bash sudo systemctl restart NetworkManager ``` ### 2. 重新获取 DHCP 分配的地址 如果您的网络配置是自动通过 DHCP 获取的,可以尝试释放当前 IP 并重新获取: ```bash sudo dhclient -r sudo dhclient ``` ### 3. 重置网络配置文件 如果上述方法都没有解决问题,可以尝试手动重置网络配置。对于使用 Netplan 的 Ubuntu 系统(通常是 Ubuntu 18.04 及以后的版本),可以按照以下步骤操作: 1. **查找 Netplan 配置文件**: ```bash ls /etc/netplan/ ``` 这将列出所有的网络配置文件。 2. **编辑或恢复默认配置**: 使用文本编辑器编辑文件,例如: ```bash sudo nano /etc/netplan/01-netcfg.yaml ``` 将内容修改为以下形式(根据实际接口名称调整 `eth0` 或其他网络接口名): ```yaml network: version: 2 ethernets: eth0: dhcp4: true ``` 保存并退出编辑器。 3. **应用配置**: ```bash sudo netplan apply ``` ### 4. 检查 DNS 设置 问题可能与 DNS 解析有关,确保 DNS 设置正确: - 查看当前 DNS 配置: ```bash systemd-resolve --status ``` - 可以尝试设置一个公共 DNS(如 Google DNS 8.8.8.8 和 8.8.4.4)来看是否解决问题。编辑 Netplan 配置文件,在适当的部分添加 DNS 服务器: ```yaml nameservers: addresses: - 8.8.8.8 - 8.8.4.4 ``` 保存并应用配置: ```bash sudo netplan apply ``` ### 5. 检查防火墙设置 有时防火墙设置可能导致无法访问特定网站。检查防火墙规则,并尝试暂时禁用防火墙: ```bash sudo ufw disable ``` 如果禁用后可以访问网页,那么问题可能在于防火墙配置。 尝试这些方法后,如果问题依然存在,可能需要更详细地检查网络环境或咨询您的网络服务提供商。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/u013559309/article/details/139553532
-
前言 今天也是超爱你的一天哦! mkdir指令 在当前目录下创建一个名为 “dirname”的目录 -p, --parents 可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后,系统将自动建立 好那些尚不存在的目录,即一次可以建立多个目录 rmdir指令 && rm 指令 rmdir是一个与mkdir相对应的命令。mkdir是建立目录,而rmdir是删除命令。 语法:rmdir [-p][dirName] 适用对象:具有当前目录操作权限的所有使用者 功能:删除空目录 常用选项: -p 当子目录被删除后如果父目录也变成空目录的话,就连带父目录一起删除。 rm命令可以同时删除文件或目录 语法:rm [-f-i-r-v][dirName/dir] 适用对象:所有使用者 功能:删除文件或目录 常用选项: -f 即使文件属性为只读(即写保护),亦直接删除 -i 删除前逐一询问确认 -r 删除目录及其下所有文件 man指令 Linux的命令有很多参数,我们不可能全记住,我们可以通过查看联机手册获取帮助。访问Linux手册页的命令是 man 语法: man [选项] 命令 -k 根据关键字搜索联机帮助 num 只在第num章节找 -a 将所有章节的都显示出来,比如 man printf 它缺省从第一章开始搜索,知道就停止,用a选项,当按 下q退出,他会继续往后面搜索,直到所有章节都搜索完毕。 解释一下,面手册分为8章 第 1 部分(man 1)包含单行命令的帮助,如 man ls; 第 2 部分(man 2)是系统调用(system calls),如 man open; 第 3 部分(man 3)涵盖库函数,例如 man malloc; 第 4 部分(man 4)是设备驱动程序和网络协议, 而第 5 部分(man 5)涉及配置文件和其他系统管理文件。 此外,还有第 6、7 和 8 部分,分别用于程序游戏规则(games)、系统文档结构和进程与线程控制等。 cp指令 语法:cp [选项] 源文件或目录 目标文件或目录 功能: 复制文件或目录 说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录, 则它会把前面指定的所有文件或目录复制到此目录中。若同时指定多个文件或目录,而最后的目的地并非一个已存 在的目录,则会出现错误信息 -f 或 --force 强行复制文件或目录, 不论目的文件或目录是否已经存在 -i 或 --interactive 覆盖文件之前先询问用户 -r递归处理,将指定目录下的文件与子目录一并处理。若源文件或目录的形态,不属于目录或符号链 接,则一律视为普通文件处理 -R 或 --recursive递归处理,将指定目录下的文件及子目录一并处理 mv指令 mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命 令,经常用来备份文件或者目录。 语法: mv [选项] 源文件或目录 目标文件或目录 功能: 1. 视mv命令中第二个参数类型的不同(是目标文件还是目标目录),mv命令将文件重命名或将其移至一个新的 目录中。 2. 当第二个参数类型是文件时,mv命令完成文件重命名,此时,源文件只能有一个(也可以是源目录名),它 将所给的源文件或目录重命名为给定的目标文件名。 3. 当第二个参数是已存在的目录名称时,源文件或目录参数可以有多个,mv命令将各参数指定的源文件均移至 目标目录中。 -f :force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 -i :若目标文件 (destination) 已经存在时,就会询问是否覆盖! less指令 less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极 其强大。 less 的用法比起 more 更加的有弹性。 在 more 的时候,我们并没有办法向前面翻, 只能往后面看 但若使用了 less 时,就可以使用 [pageup][pagedown] 等按键的功能来往前往后翻看文件,更容易用 来查看一个文件的内容! 除此之外,在 less 里头可以拥有更多的搜索功能,不止可以向下搜,也可以向上搜 语法: less [参数] 文件 功能: less与more类似,但使用less可以随意浏览文件,而more仅能向前移动,却不能向后移动,而且less在查看之前 不会加载整个文件。 -i 忽略搜索时的大小写 -N 显示每行的行号 /字符串:向下搜索“字符串”的功能 ?字符串:向上搜索“字符串”的功能 n:重复前一个搜索(与 / 或 ? 有关) N:反向重复前一个搜索(与 / 或 ? 有关) q:quit find指令 Linux下find命令在目录结构中搜索文件,并执行指定的操作。 Linux下find命令提供了相当多的查找条件,功能很强大。 由于find具有强大的功能,所以它的选项也很 多,其中大部分选项都值得我们花时间来了解一下。 即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限。 在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系 统可能会花费很长的时间(这里是指30G字节以上的文件系统)。 语法: find pathname -options 功能: 用于在文件树种查找文件,并作出相应的处理(可能访问磁盘) 常用选项:-name 按照文件名查找文件。 tar指令:打包/解包,不打开它,直接看内容 tar [-cxtzjvf] 文件与目录 .... 参数: -c :建立一个压缩文件的参数指令(create 的意思); -x :解开一个压缩文件的参数指令! -t :查看 tarfile 里面的文件! -z :是否同时具有 gzip 的属性?亦即是否需要用 gzip 压缩? -j :是否同时具有 bzip2 的属性?亦即是否需要用 bzip2 压缩? -v :压缩的过程中显示文件!这个常用,但不建议用在背景执行过程! -f :使用档名,请留意,在 f 之后要立即接档名喔!不要再加参数! -C : 解压到指定目录 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/ormstq/article/details/140313132
-
一、什么是 CPU 使用率 Linux 作为一个多任务操作系统,将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉。 为了维护 CPU 时间, Linux 通过事先定义的节拍率(内核中表示为 HZ),触发时间中断,并使用全局变量 Jiffies 记录了开机以来的节拍数。每发生一次时间中断,Jiffies 的值就加 1。节拍率 HZ 是内核的可配选项,可以设置为 100、250、1000 等。不同的系统可能设置不同数值。 Linux 通过 /proc 虚拟文件系统,向用户空间提供了系统内部状态的信息,而 /proc/stat 提供的就是系统的 CPU 和任务统计信息。比方说,如果你只关注 CPU 的话,可以执行下面的命令: 第一列表示的是 CPU 编号,如 cpu0、cpu1 ,而第一行没有编号的 cpu ,表示的是所有 CPU 的累加结果。其他列则表示不同场景下 CPU 的累加节拍数,它的单位是 USER_HZ,也就是 10 ms(1/100 秒),所以这其实就是不同场景下的 CPU 时间。下面,依次为各列的含义: user(通常缩写为 us),代表用户态 CPU 时间。注意,它不包括下面的 nice时间,但包括了 guest 时间。 nice(通常缩写为 ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间。这里注意,nice 可取值范围是 -20 到 19,数值越大,优先级反而越低。 system(通常缩写为 sys),代表内核态 CPU 时间。 idle(通常缩写为 id),代表空闲时间。注意,它不包括等待 I/O 的时间(iowait)。 iowait(通常缩写为 wa),代表等待 I/O 的 CPU 时间。 irq(通常缩写为 hi),代表处理硬中断的 CPU 时间。 softirq(通常缩写为 si),代表处理软中断的 CPU 时间。 steal(通常缩写为 st),代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间。 guest(通常缩写为 guest),代表通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的 CPU 时间。 guest_nice(通常缩写为 gnice),代表以低优先级运行虚拟机的时间。 而我们通常所说的 CPU 使用率,就是除了空闲时间外的其他时间占总 CPU 时间的百分比,用公式来表示就是: C P U 使用率 = 1 − 空闲时间 总 C P U 时间 CPU 使用率 = 1 - \frac{空闲时间}{总 CPU时间} CPU使用率=1− 总CPU时间 空闲时间 但直接用 /proc/stat 的数据这是开机以来的节拍数累加值,所以直接算出来的,是开机以来的平均 CPU 使用率,一般没有什么参考价值。 事实上,为了计算 CPU 使用率,性能工具一般都会取间隔一段时间(比如 1 秒)的两次值,作差后,再计算出这段时间内的平均 CPU 使用率,即: 平均 C P U 使用率 = 1 − 空闲时 间 n e w − 空闲时 间 o l d 总 C P U 时 间 n e w − 总 C P U 时 间 o l d 平均 CPU 使用率 = 1 - \frac{空闲时间_{new} - 空闲时间_{old}}{总 CPU时间_{new} - 总 CPU时间_{old}} 平均CPU使用率=1− 总CPU时间 new −总CPU时间 old 空闲时间 new −空闲时间 old 跟系统的指标类似,Linux 也给每个进程提供了运行情况的统计信息,也就是 /proc/[pid]/stat。不过,这个文件包含的数据就比较丰富了,总共有 52 列的数据,这里不再赘述。 二、查看 CPU 利用率 1、使用 top 查看 top 是一个命令行工具安装在任何 Linux 系统上,它主要提供由 Linux 内核管理的所有当前运行任务的动态实时统计汇总。它监视 Linux 系统上进程、CPU 和内存的完整利用率。 $ top 1 结果如下: us: 花费在用户空间上的 CPU 时间百分比 (running user-spawned processes)。 sy: 花费在内核空间的 CPU 时间百分比(running system processes)。 ni: 运行用户定义优先级的进程所花费的 CPU 时间百分比(aspecified nice value)。 id: CPU 空闲时间的百分比。 wa: 用于等待硬件 I/O 的 CPU 时间百分比。例如:等待硬盘读完数据。 hi: 用于处理硬件中断的 CPU 时间百分比。例如:网卡(或任何硬件)中断 CPU 以通知它有新数据到达。 si: 用于处理软件中断的 CPU 时间百分比。例如:高优先级业务导致 CPU 中断。 st: 从虚拟机窃取的 CPU 时间百分比。例如:为了处理物理机的工作负载,需要从虚拟机“窃取”资源的 CPU 可以非常清楚地看到 CPU 使用率。不过,要注意的是,CPU 默认显示的是所有 CPU 的平均值。这时候,按下数字 1 就可以看到每个 CPU 的使用率了: 继续往下看,空白行之后是进程的实时信息,每个进程都有一个 %CPU 列,表示进程的 CPU 使用率。它是用户态和内核态 CPU 使用率的总和,包括进程用户空间使用的 CPU、通过系统调用执行的内核空间 CPU 、以及在就绪队列等待运行的 CPU。在虚拟化环境中,它还包括了运行虚拟机占用的CPU。 可以发现,top 并没有细分进程的用户态 CPU 和内核态 CPU。 2、用 pidstat 查看 pidstat 是一个专门分析每个进程 CPU 使用情况的工具。比如,下面的 pidstat 命令,就间隔 1 秒展示了进程的 5 组 CPU 使用率,包含: 用户态CPU使用率(%usr); 内核态CPU 使用率(%system) 运行虚拟机CPU使用率(%guest) 等待 CPU使用率(%wait); 总的CPU使用率(%CPU) 最后的 Average 部分,还计算了5组数据的平均值。 3、用 ps 查看 ps 命令可用于确定哪个进程占用了 CPU。 $ ps -eo pid,%cpu,cmd --sort=-%cpu 1 如果要仅查看正在运行的进程,可以使用以下命令: $ ps -eo pid,%cpu,cmd --sort=-%cpu | grep -v PID 1 这个命令将过滤掉标题行。 如果要仅显示前几个进程,可以将输出通过head命令进行截取。例如,要显示前 5 个进程,可以使用以下命令: $ ps -eo pid,%cpu,cmd --sort=-%cpu | grep -v PID | head -n 5 1 4、用 htop 查看 htop 是一个交互式的进程查看器和系统监控工具,它提供了比传统的 top 命令更多的功能和更友好的界面。它提供了 CPU 和系统资源利用率的详细摘要。可以垂直滚动或水平滚动以显示更多详细信息。它还在命令列下提供进程路径。 $ htop 1 在 CPU 状态区域主要显示 CPU 使用情况,htop 还为将不同颜色来区分是使用情况: 蓝色的表示 low-prority(低优先级)使用 绿色的表示 normal(标准)使用情况 红色的表示 kernel(内核)使用情况 青色的表示 virtuality(虚拟性)使用情况 5、用 nmon 查看 nmon 是 Linux 系统的一个性能监控工具。它用于实时监测系统的 CPU、内存、磁盘、网络等资源的使用情况,帮助管理员分析系统性能并做出优化调整。 nmon 工具以文字界面的形式展示监控结果,提供了丰富的信息和统计数据,可以实时查看系统的运行状况。它的输出格式清晰简洁,容易理解和分析。 使用 nmon 命令: $ nmon 1 然后按 t 查看利用更多资源的进程: 按下 c 键可以来查看 CPU 的使用率: 6、用 atop 查看 atop 是在 Linux 系统的一个高级性能监控工具。与 nmon 类似,atop 也用于实时监测系统的各种资源的使用情况,但它提供了更为详细和全面的性能数据,让管理员更深入地了解系统运行情况。 $ atop 1 也可以使用以下命令把 CPU 使用率记录到文件中,下次直接读取文件查看 CPU 利用率。写入文件使用“-w 文件名”,读出文件使用“-r 文件名”: $ root@projectsauron:~# atop -w test ^C $ root@projectsauron:~# atop -r test 1 2 3 7、用 glances 查看 glances 是一种跨平台的实时系统监控工具,该工具是用python编写的,并使用库程序详细提供了对 CPU、内存、磁盘、网络和进程等系统资源的监测信息。glances 以直观和交互的方式呈现系统监控数据,并提供了丰富的选项和功能,方便用户进行系统性能的实时监控和分析。 8、用 vmstat 查看 vmstat(virtual memory statistics)是一个在 Linux 系统上用于监视虚拟内存、进程、CPU 和 IO 性能的命令行工具。 $ vmstat 1 各个字段你含义如下: 进程 procs r:在运行队列中等待的进程数 。 b:在等待io的进程数 。 内存 memoy: swpd:现时可用的交换内存(单位KB)。 free:空闲的内存(单位KB)。 buff: 缓冲去中的内存数(单位:KB)。 cache:被用来做为高速缓存的内存数(单位:KB)。 swap 交换页面 si: 从磁盘交换到内存的交换页数量,单位:KB/秒。 so: 从内存交换到磁盘的交换页数量,单位:KB/秒。 io 块设备: bi: 发送到块设备的块数,单位:块/秒。 bo: 从块设备接收到的块数,单位:块/秒。 system 系统: in: 每秒的中断数,包括时钟中断。 cs: 每秒的环境(上下文)转换次数。 cpu 中央处理器: cs:用户进程使用的时间 。以百分比表示。 sy:系统进程使用的时间。 以百分比表示。 id:中央处理器的空闲时间 。以百分比表示。 如果想使用 vmstat 命令以 2 秒的间隔监视系统资源,间隔 5 次。在这种情况下,可以使用以下命令: $ vmstat 2 5 1 9、用 sar 查看 sar(System Activity Reporter)是一个在 Linux 系统上用于收集、报告和存档系统活动数据的命令行工具。 使用 sar 命令,可以按特定时间间隔监视 CPU 使用率: $ sar 3 1 也可以通过如下来显示每隔 3 秒 10 条后的运行数据。 $ sar 3 10 1 10、dstat dstat 是一个在 Linux 系统上使用的用于监控系统资源使用情况的命令。它可以提供关于 CPU、内存、磁盘、网络等方面的实时数据,并以可读性高的格式输出。该工具结合了 vmstat,iostat,ifstat,netstat 以及更多的信息。并且支持输出 CSV 格式报表,并能导入到 Gnumeric 和 Excel 以生成图形 $ dstat 1 每秒 CPU 使用率情况获取: $ dstat -c 1 最占 CPU 的进程获取: $ dstat --top-cpu 1 11、iostat 这个命令主要用来查看 IO 使用情况,也可以来查看 CPU,不如上述命令常用。 三、总结 Linux 系统上有各种可用的工具和命令来监视 CPU 使用率和系统资源利用率,那么多命令工具可根据实际情况,直观可视化的 nmon 和 galances 比较方便查看结果。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/Teminator_/article/details/140999346
-
1. 进程创建 1.1. 操作系统的工作内容 当一个进程调用fork函数创建一个新的子进程时,操作系统会执行以下主要步骤:分配资源 -> 复制信息 ->添加到系统进程列表 -> 返回值 -> 调度器调度。 分配资源:OS给子进程分配新的内存块(虚拟地址空间),和其他必要的内核数据结构(PCB、页表等)。 复制信息:将父进程的数据结构内容的一部分拷贝给子进程。 添加到系统进程列表:OS会将子进程的信息添加到系统的进程管理数据结构中(进程链表等),这样系统就能够管理和跟踪该子进程的状态和行为,调度器就能够感知新的进程并对其进行调度。 返回值:为了区分父子进程,并允许它们执行不同的代码路径(if…else分流)。 调度器调度:一旦fork操作完成,OS调度器就会重新安排CPU时间片,调度器可能会根据调度算法选择父进程、子进程、或系统中其他进程来执行,这意味着父进程,子进程可能会并发执行,但它们的执行顺序是不确定的。 1.2. fork常规用法 父进程希望复制自己,根据fork返回值使用if…else进行分流,从而使父子进程执行相同程序中不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求。 子进程要执行与父进程完全不同的程序。在这种情况下,子进程从fork返回后,通过调用exec()系列函数,在当前进程中加载并运行一个新的程序(进程程序替换)。例如:需要执行特定任务的子进程,这些任务与父进程的主要职责不同。 1.3. fork调用失败的原因 系统中有太多进程。如:一个学校能容纳的学生总数是有限的。 实际用户的进程数超过了限制。如:一个班级能容纳的学生总数是有限的。 2. 进程终止 2.1. main函数的返回值 2.1.1. 退出码 退出码:main函数的返回值,用来表示进程退出(终止)时,其执行结果是否正确。 main函数返回0,表示代码执行成功,结果正确或者符合预期的。 main函数返回非0,表示代码执行成功,结果是不正确的或程序遇到错误/异常情况。 代码执行成功,程序能够执行到main函数的末尾并返回,而不是说程序中的每一行都按预期执行了,因为有些错误不能被捕获或者导致程序提前退出了。 非0返回值,通常用于表示不同类型错误/异常的原因,退出码的字符串含义取决于程序的设计者。 Shell(bash)会自动记录最近一次子进程执行完毕时的退出码,这个退出码可以通过echo $?获取。 echo $? 功能:获取上一个命令的退出码。 #include<stdio.h> int main() { printf("hello world\n"); return 0; //退出码 } 2.1.2. 退出码转化为错误描述的方式 使用语言或者系统自带的方法进行转化,例如:在linux中,使用strerror()函数。 char* strerror(int errnum); #include<stdio.h> #include<string.h> int main() { for(int i = 0; i < 100; i++) printf("%d:%s\n", i, strerror(i)); return 0; } 2. 使用枚举类型进行自定义。 #include<stdio.h> #include<string.h> enum{ success=0, malloc_err, open_err }; const char* errorDesc(int code) { switch(code) { case success: return "running sucess!"; case malloc_err: return "malloc failure!"; case open_err: return "file open failure!"; default: return "unkown error!"; } } int main() { int exit_code = malloc_err; printf("%s\n", errorDesc(exit_code)); return 0; } 2.2. 普通函数的返回值 执行结果:文件打开成功,fopen()返回指向该文件的指针;文件打开失败,fopen()返回NULL。 执行情况:返回了非空的FILE*指针,则可认为函数执行成功;返回了NULL,则可认为函数执行失败,需要进一步检查错误的原因(errno变量或调用perror()函数)。 普通函数退出,仅仅表示函数调用完毕。 函数也被称为子程序,与进程退出时返回退出码类似,函数执行完毕也会返回一个值,这个值通常用于表示函数的执行结果或状态。 调用函数,我们通常想看到两种结果:a.函数的执行结果(函数的返回值);b.函数的执行情况(函数是否成功执行了预期的任务),例如:fopen()函数的执行情况是通过其执行结果来间接表示。 2.2.1. 错误码 errno是错误码,它是记录系统最后一次错误代码的一个整数值,不同值表示不同含义,在#include<errno.h>中定义。 当函数运行成功时,errno值不会被修改,因此我们不能通过测试errno的值来判断是否有错误存在,而应该在被调用的函数提示有错误发生时,再检查errno的值。 💡Tips:只有当库函数失败时,errno的值才会被设置。 #include<stdio.h> #include<string.h> #include<errno.h> int main() { FILE* fp = fopen("log.txt","r"); if(fp == NULL) printf("%d:%s\n", errno, strerror(errno)); return 0; } 2.3. 进程退出的场景 退出码用于表示程序的执行结果或者终止的原因。 退出信号(kill)用于指示进程是正常退出,还是被信号杀死(每个信号都有不同的编号,编号表明异常的原因)。 代码执行完毕,结果正确 -> 退出信号=0、退出码=0。 代码执行完毕,结果不正确 -> 退出信号=0、退出码=!0。 代码没有执行完毕,进程异常终止 -> 退出信号=!0、退出码无意义。 💡Tips:任何进程最终的执行情况,我们都可以用两个数字来表明具体的执行情况,这两个数字分别为退出码、退出信号。 2.4. 进程退出的方式 2.4.1. main函数的返回 当程序执行到main函数的末尾,或者遇到return语句时,程序会返回main函数的返回值(退出码)。 mian函数返回是程序主动退出的方式,即:正常终止进程。 2.4.2. 调用exit()、_exit()函数 一、exit()函数 void exit(int status); 调用exit()是程序主动退出的方式,即:正常终止进程。 exit()函数是C标准库提供的一个函数,在#include<stdlib.h>中定义,用于立即终止当前进程的执行,它会接受一个整形作为参数,该整形为进程的退出码。 调用exit(),程序会立即终止,exit()同时会执行清理操作(如:刷新所有的输出缓冲区、关闭通过fopen打开的文件、malloc开辟的内存等),然后向操作系统返回退出码。 二、_exit()函数 void _exit(int status); 调用_exit()是程序主动退出的方式,即:正常终止进程。 _exit()函数是系统调用函数,在#include<unistd.h>中定义,用于立即终止当前进程的执行,它会接受一个整形作为参数,该整形为进程的退出码。 _exit()不会自动执行exit()函数所执行的清理工作,需要确保在调用它之前,手动处理所有必要的清理工作,然后向操作系统返回退出码。 💡注意:main函数返回、调用exit()、_exit()函数,都表示程序主动退出,即:正常终止;接受到信号(如:ctrl c,信号终止),表示程序被动退出,即:异常退出。 2.4.3. exit()、_exit()函数的区别 exit()支持刷新缓冲区,_exit()不支持刷新缓冲区,因为exit中有缓存区,_exit中无缓冲区。 它们都是终止进程,但只有OS才有能力终止进程,因此exit()底层封装了_exit(),两者是上下层关系,。 💡Tips:我们之前谈及的缓冲区(进度条之类),绝对不是操作系统级别的缓冲区,是标准C库管理的缓冲区(库级别的缓冲区)。 为什么语言具有可移植性和跨平台性?在库层面上,对系统强相关的接口进行了封装,从而屏蔽了底层差异。 3. 进程等待 3.1. 必要性 子进程先退出,如果父进程不回收其资源,子进程就变成僵尸状态,此状态无法被kill -9杀死,因为无法杀死已经死掉的进程,从而造成内存泄漏。 父进程需要知道派给子进程的任务完成的如何,即:获取子进程的退出信息。 子进程的退出信息(exit code、exit signal),需要通过内核数据结构来维护,保存在子进程的task_struct中,属于内核数据。 💡Tips:总结:父进程通过进程等待的方式,回收子进程资源(必然),获取子进程的退出信息(可选)。 3.2. 方式 3.2.1. wait pid_t wait(int* status); 功能:等待任意一个子进程结束,并回收其资源。 返回值:调用成功,返回已经结束进程的PID,同时获取到了子进程的退出状态码;调用失败,返回-1,并设置错误码以指示错误的原因。 参数status:输出型参数,用于存储子进程的退出状态,由OS填充,如果不需要这个信息,可以传递NULL,否则,OS会根据该参数,将子进程的信息反馈给父进程。 3.2.2. waitpid pid_t waitpid(pid_t pid, int* status, int options); 功能:等待任意一个子进程或者指定的子进程结束,并回收其资源。 参数pid:如果pid = -1,等待任意一个子进程,与wait等效;如果pid > 0,等待其进程的PID与pid相等的子进程。 参数option:如果option = 0,则为阻塞等待;如果option = WNOHANG,则为非阻塞等待。 返回值:调用成功,返回收集到的子进程的PID,同时获取到了子进程的退出状态码;调用失败,返回-1,并设置错误码以指示错误的原因;如果为非阻塞等待,waitpid调用成功且没有收集到已结束的子进程,则返回0。 3.3. 阻塞等待、非阻塞状态 一、阻塞等待 定义:进程在发出某个请求(如:I/O操作、等待某个条件成立等)后,如果请求不能立即得到满足(如:数据未准备好、资源被占用等),进程会被挂起,在此期间无法继续执行其他任务,直到等待条件满足或被唤醒。 故事理解:张三找李四辅导c语言,张三打电话给李四叫他下来,但李四正在寝室中复习期末,李四在复习的期间,和张三一直通着电话,张三不能做任何其他事情。打电话的过程 == 系统调用。 特点: a.行为 -> 进程在等待期间无法执行其他任务。 b.触发方式 -> 等待由外部条件触发(如:数据到达、资源释放等)。 c.管理层面:由操作系统或者底层系统资源管理。 d.效率与并发性:效率低。 应用场景:实时性要求不高,等待时间相对比较短的情况,如:简单文件的读写操作。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() { pid_t id = fork(); if(id == 0) //子进程 { int cnt = 5; while(cnt) { printf("child is running, id:%d, ppid:%d\n", getpid(), getppid()); sleep(1); cnt--; } exit(1); //子进程退出 } int status = 0; //存储子进程退出状态 pid_t rid = waitpid(id, &status, 0); //父进程等待 —— 阻塞等待 if(rid > 0) //等待成功 printf("wait success, status:%d\n", status); else if(rid == -1) //调用失败 perror("wait error!\n"); return 0; } 二、非阻塞等待 定义:进程在发出某个请求后,不会被立即挂起已等待请求的完成,即使请求不能立即得到满足,进程在等待期间可以继续执行其他任务,同时可能会以某种方式(轮询访问、回调等)定期检查请求状态或者等待结果的通知。 故事理解:张三找李四辅导c语言,张三打电话给李四叫他下来,但李四正在寝室中复习期末,电话挂断,李四在复习的期间,张三可以做其他的事情,并定期多次打电话给李四,询问是否复习完毕。 特点: a.行为 -> 进程在等待期间可以执行其他任务; b.触发方式 -> 可能通过编程的方式实现,如:轮询、回调等。 c.管理层面:在应用层通过编程实现。 d.效率与并发性:效率高,提高并发性和响应能力。 应用场景:需要高并发和响应能力的场景,如:在网络编程中,服务器同时处理多个客户端的请求。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #define SIZE 5 typedef void(*fun_t)(); //函数指针类型 fun_t task[SIZE]; //函数指针数组 void printlog() { printf("this is a log print task\n"); } void printnet() { printf("this is a net task\n"); } void printNPC() { printf("this is a flush NPC task\n"); } void Inittask() { task[0] = printlog; task[1] = printnet; task[2] = printNPC; task[3] = NULL; } void executeTask() { for(int i = 0; task[i]; i++) task[i](); //回调函数机制 } int main() { Inittask(); pid_t id = fork(); if(id == 0) //子进程 { int cnt = 2; while(cnt) { printf("I am a process, id:%d, ppid:%d\n", getpid(), getppid()); sleep(1); cnt--; } exit(1); //子进程退出 } int status = 0; //存储子进程退出状态 while(1) //基于非阻塞轮询的访问 { pid_t rid = waitpid(id, &status, WNOHANG); //非阻塞等待 if(rid > 0) //调用成功,收集到了已经结束的子进程 { printf("wait success, status:%d\n", status); break; } else if(rid == 0) //调用成功,未收集到已经结束的子进程 { printf("child is running, father do other thing!\n"); printf("------------ Task begin ----------------\n"); executeTask(); //等待期间,执行其他任务 printf("------------ Task end ----------------\n"); } else //调用失败 { perror("wait error\n"); break; } sleep(1); } return 0; } 3.4. 获取子进程 status不能简单的当作整形来看,可以当作位图看待,它有自己的格式,只研究status低16位比特位。 3.4.1. 位操作 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() { pid_t id = fork(); if(id == 0) //子进程 { int cnt = 5; while(cnt) { printf("child is running, id:%d, ppid:%d\n", getpid(), getppid()); sleep(1); cnt--; } exit(1); //子进程退出 } int status = 0; //存储子进程退出状态 pid_t rid = waitpid(id, &status, 0); if(rid > 0) //等待成功 printf("wait success, status:%d, exit code:%d, exit sign:%d\n", status, (status>>8)&0xff, status&0x7f); //位操作获取子进程的退出码、退出信号 return 0; } 3.4.2. 宏 WIFEXITED(status):检查子进程是否正常退出。 如果子进程通过调用exit函数或main函数return返回而退出,则WIFEXITED返回非0值(真)-》正常退出;如果子进程是由于接收到信号而退出,则WIFEXITED返回0(假)-》异常退出。 WEXITSTATUS(status):只有当WIFEXITED为真时,接着才会使用WEXITSTATUS获取子进程的退出码。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() { pid_t id = fork(); if(id == 0) //子进程 { int cnt = 5; while(cnt) { printf("child is running, id:%d, ppid:%d\n", getpid(), getppid()); sleep(1); cnt--; } exit(1); //子进程退出 } int status = 0; //存储子进程退出状态 pid_t rid = waitpid(id, &status, 0); if(rid > 0) //等待成功 { if(WIFEXITED(status)) //子进程正常退出 printf("wait success, status:%d, exit code:%d\n", status, WEXITSTATUS(status)); //提取退出码 宏 else //子进程异常退出 printf("child process error!\n"); } return 0; } 问题1:在父进程中定义两个全局变量(exit code、exit sign),子进程修改exit code值,父进程可以获取到子进程的退出信息吗? 不能。因为进程具有独立性,子进程对共享数据的修改,父进程是不可见的。 问题2:为什么要有wait、waitpid? 为了避免子进程僵尸,造成内存泄漏,父进程需要通过wait、waitpid等函数来回收子进程资源,同时可以获取到子进程的退出信息。 子进程的退出码、退出信号等内核数据,需要被拷贝到用户层的某个变量(如:wait、waitpid中的status参数等),这个过程需要调用系统调用接口,因为用户空间的程序无法直接访问内核空间的数据。 4. 进程程序替换 4.1. 概念与原理 概念:它允许一个进程在执行期间,用一个新的程序来替换当前正常执行的程序,即:用全新的程序替换原有的程序。 这意味着进程在调用一种exec函数,当前进程的用户空间代码和数据被新程序的代码和数据完全替换(覆盖),从新程序的启动例程开始执行。 💡Tips:调用exec函数,并不会创建新的进程,而是对原有进程的资源进行替换,因此调用exec前后该进程的pid并未发生改变。 原理:加载新程序 -> 替换当前程序 -> 更新页表 -> 执行新程序。 加载新程序:当进程决定进行程序替换时(调用exec函数),它会请求OS将全新程序(代码和数据)从磁盘中加载到内存。 更新页表:为了实现替换,OS需要更新页表,将原来指向旧程序代码的虚拟地址映射到新程序代码的物理地址上,这样,就会执行新程序的代码。 所谓的把磁盘的数据加载到内存,把磁盘的数据拷贝到内存中,磁盘,内存都是硬件,只有操作系统具有将数据从一个硬件(磁盘)搬移到另一个硬件(内存)的能力,从而支持程序的加载和替换。 💡注意:进程替换的本质工作就是加载,充当的是加载器的角色! 4.2. 替换函数exec* l(list):有l表示命令行参数采用列表;v(vector):有v表示命令行参数采用数组; p(path):有p自动去环境变量中搜索,允许只写程序名); e(env):有e表示设置全新的环境变量,需要自己维护。 int execl(const char* path,const char* arg,. . .); 解释:以列表的形式传递命令行参数,最后必须以NULL结尾,第一个参数必须是程序的绝对路径。 #include<stdio.h> #include<unistd.h> int main() { printf("I am a process\n"); //以列表的形式传参(l); 命令行怎么写,参数怎么传(可变参数列表),但结尾必须以NULL结尾 execl("/usr/bin/ls", "ls", "-l", NULL); return 0; } int execv(const char* path,char* const argv[ ]); 解释:以数组的形式传递命令行参数,将命令行参数存储在以NULL指针结尾的指针数组中。 char* const argv[] = {(char*)"ls", (char*)"-l", NULL}; //存储命令行参数的以NULL结尾的指针数组 //以数组的形式传参(v) execv("/usr/bin/ls", argv); int execlp(const char* file,const char* arg,. . .); int execvp(const char* file,char* const argv[ ]); 解释:第一个参数允许用户只提提供程序名,在替换时,自动去环境变量PATH指定的路径中查找程序。 char* const argv[] = {(char*)"ls", (char*)"-l", NULL}; //存储命令行参数的以NULL结尾的指针数组 //有p自动去环境PATH中搜索,允许第一个参数只写程序名 execlp("bin", "ls", "-l", "NULL"); execvp("bin", argv); int execle(const char* path,const char* arg,. . . ,char* const envp[ ]); int execvpe(const char* file,char* const argv[ ],char* const envp[ ]); int execve(const char* filename,char* const argv[ ],char* const envp[ ]); 解释:最后一个参数为自己维护的环境变量(可设置全新的环境变量表)。 #include<stdio.h> #include<unistd.h> int main() { printf("I am a process\n"); char* const env[] = {(char*)"haha=hehe", (char*)"zzx=lala", NULL}; //自己维护环境变量 execle("./mytest", "-a", "-b", NULL, env); //设置全新的环境变量 return 0; } #include<stdio.h> int main(int argc, char* argv[], char* env[]) { for(int i = 0; argv[i]; i++) printf("argv[%d]:%s\n", i, argv[i]); for(int i = 0; env[i]; i++) printf("env[%d]:%s\n", i, env[i]); return 0; } 4.2.1. 细节 替换完成,不会创建新的进程。 程序一旦替换成功,exec*后续的代码不在执行。 exec*函数只有出错的返回值,没有成功的返回值。 exec*函数调用失败,返回-1,并设置错误码以指示错误的原因。 进程替换本身不会改变环境变量的数据。 子进程会自动继承父进程的环境变量,子进程通过exec函数进行程序替换,加载并执行新的程序,这个新的程序会继承子进程所拥有的环境变量。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/m0_74808907/article/details/141950474
-
前言: 本文介绍的是有关地址空间,咱们的介绍的大体思路是,先直接看现象,通过现象我们引入地址空间的概念,然后是简单的介绍地址空间,如何理解地址空间等,在往后的学习中,我们大概会介绍地址空间3 - 4次,因为地址空间有很多内容一次性还介绍不完,并且在本文中,我们能够理解之前颠覆代码三观的函数——fork(),现在就进入正题。 代码现象 目前我们对于地址空间没有明确的概念,所以先来看这样一段代码: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include<sys/types.h> int g_val =100; int main() { pid_t id = fork(); if(id == 0) { int count = 1; while(count) { printf("g_val is %d,&g_val = %p\n",g_val,&g_val); sleep(1); if(count == 5) g_val = 200; count++; } } else { while(1) { printf("g_val is %d,&g_val = %p\n",g_val,&g_val); sleep(1); } } return 0; } 代码的意思是,我们创建一个父进程之后,在父进程里面创建一个子进程,子进程要完成的工作是打印g_val和它的地址,当count到5的时候就修改g_val,但是后续还是要一直打印,父进程要做的工作就是一直打印g_val和g_val的地址。 现象如下: 打印5秒之后,g_val的值如愿以偿的被修改了,此时让父进程打印的时候,我们发现一个怪事,打印的时候为什么父进程中的g_val没有变化呢?我们在进程部分知道父进程的数据是和子进程共享的,但是此时,父进程的数据被子进程修改了,父进程居然无动于衷? 现在的现象就是:一个变量,地址没有变化,但是拥有两个不同的值。 我们一会儿要理解的就是该现象,该现象理解了之后,我们同时就能理解fork函数的返回值是怎么回事了。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2301_79697943/article/details/142466903
-
进程创建 fork 1.fork 之后发生了什么 将给子进程分配新的内存块和内核数据结构(形成了新的页表映射) 将父进程部分数据结构内容拷贝至子进程 添加子进程到系统进程列表当中 fork 返回,开始调度器调度 这样就可以回答之前返回两个值? 发生了写实拷贝,形成了两个物理空间块 测试 #include <stdio.h> #include <unistd.h> #include <stdlib.h> int main(void) { printf("Before -> pid: %d\n", getpid()); fork(); printf("After -> pid: %d\n", getpid()); sleep(1); return 0; } 创建了一个子进程 一般来说子进程创建之后,会共享父进程的所有代码 是怎么知道的呢?没关系,eip 程序计数器会出手! eip 叫做 程序计数器,用来保存当前正在执行的指令的下一条指令。eip 程序计数器会拷贝给子进程,子进程便从该 eip 所指向的代码处开始执行。 我们再来重新思考一下 fork 之后操作系统会做什么: " 进程 = 进程的数据结构 + 进程的代码和数据 " 创建子进程的内核数据结构: (struct task_struct + struct mm_struct + 页表)+ 代码继承父进程,数据以写时拷贝的方式来进行共享或者独立。 代码共享,写实拷贝确保了进程的独立性 写实拷贝 当任意一方试图写入,就会按照写时拷贝的方式各自拷贝一份副本出来。写时拷贝本身由操作系统的内存管理模块完成的。 选择暂时先不给你,等你什么时候要用什么时候再给。这就变相的提高了内存的使用情况。 fork fork 之后利用 if-else 进行分流, 让父子执行不同的代码块。我们做网络写服务器的时候会经常采用这样的编码方式,例如父进程等待客户端请求,生成子进程来处理请求。 继承大纲后又有所区别 fork 肯定不是永远都成功的,fork 也是有可能调用失败的。 测试 #include <stdio.h> #include <unistd.h> #include <stdlib.h> int main(void) { for (;;) {//写了一个死循环 pid_t id = fork(); if (id < 0) { printf("子进程创建失败!\n"); break; } if (id == 0) { printf("I am a child... %d\n", getpid()); sleep(2); // 给它活2秒后 exit exit(0); // 成功就退出 } } return 0; } 进程终止 exit 为什么要使用return? 思考:代码运行完的结果一般有以下三种: 代码运行完毕,结果正确 代码运行完毕,结果不正确 代码异常中止 成功了就是成功了,失败了就会有各种原因 我们重点来对不正确进行思考 进程中,谁会关心我运行的情况呢? 父进程 怎么表示不同的出错原因呢? A: 进程的退出码 return 0 表示正确 main函数的返回值本质:表示进程运行完成时,是否是正确的结果,如果不是,我们可以用不同的数字表示出错的原因 模拟一个逻辑的实现 $? : 保存的是最近的一次进程退出的时候的退出码 我们想要进步,不再是随便无脑 return 了,我该怎么办呢? 一般而言,失败的非零值我该如何设置呢?非零值默认表达的含义又是什么呢? 首先,失败的非零值是可以自定义的,我们可以看看系统对于不同数字默认的 错误码 是什么含义。C 语言当中有个的 string.h 中有一个 strerror 接口 如果感兴趣可以看看 2.6.32 的内核代码中的 /usr/include/asm-generic/errno.h 及 errno-base.h,输出错误原因定义归纳整理如下: #define EPERM 1 /* Operation not permitted */ #define ENOENT 2 /* No such file or directory */ #define ESRCH 3 /* No such process */ #define EINTR 4 /* Interrupted system call */ #define EIO 5 /* I/O error */ #define ENXIO 6 /* No such device or address */ #define E2BIG 7 /* Argument list too long */ #define ENOEXEC 8 /* Exec format error */ #define EBADF 9 /* Bad file number */ 查看错误码 strerror(i) 我们可以在 Linux 下写个程式去把这些错误码给打印出来: 结果如下: 系统提供的错误码和 添加图片 const char *errString[]={ "success", "error 1", "error 2", "error 3" }; 实践: 对于 ls myfile.txt 的查看 $?: 2 其中,0 表示 success,1 表示权限不允许,2 找不到文件或目录。 我们刚才 ls 一个不存在的,再 echo $? 显示对应的错误码就是 2: 所以无论正确还是不正确,统一会采用进程的退出码来进行判定 不同的错误码,方便我们定位问题出在哪里 我们可以通过以下方法来查看 C 语言中 errno - number of last error 打印错误 strerror(errno) ?:如果代码异常了,退出码还有意义吗? 本质可能就是代码没有跑完,都跑不到那个地方了 进程的退出码无意义了,因为可能都到不了 return ? : 那么如何知道发生了什么异常呢? 发信号 eg. 野指针 举例 8 11 表示异常 kill -8 出现野指针 -11 段错误 终止进程的做法: #include<stdlib.h> exit 的退出码 12 exit 在任意地方被调用,都表示调用进程直接退出 return 只表示当前函数的返回 测试 #include <stdio.h> #include <stdlib.h> void func() { printf("hello func\n"); exit(111); } int main(void) { func(); return 10; } 注意,只有在 main 函数调 return 才叫做 进程退出,其他函数调 return 叫做 函数返回。 _exit 和 exit exit 会清理缓冲区,关闭流等操作,而 _exit 什么都不干,直接终止。 void func() { printf("hello exit"); exit(0); } int main(void) { func(); printf("hello _exit"); _exit(0); } 是否冲刷缓冲区的区别 缓冲区(Buffer)的概念在计算机科学中非常广泛,但在你提供的上下文中,缓冲区指的是一种用于临时存储数据的内存区域。在介绍内核的数据结构缓冲池时,缓冲区的概念主要与提升性能和合理利用内存资源有关。 具体来说,你提供的描述强调了以下几点: 开辟空间和初始化有成本: 在操作系统中,每次为新进程或新数据结构开辟内存空间并初始化都需要花费时间和资源,这会影响系统性能。 废弃的数据结构链表: 为了优化这种开销,Linux 操作系统会维护一张废弃的数据结构链表。这个链表上存放的是那些已经被标记为“无效”的数据结构,但其内存空间并没有被立即释放。这些数据结构包括 task_struct 和 mm_struct 等。 重用策略: 当一个进程被释放(即终止)后,它的相关数据结构不会立即被完全删除,而是被标记为无效并加入废弃的数据结构链表中。 当有新的进程创建时,操作系统会首先检查这个链表,从中取出一个合适的、已经存在的但无效的数据结构(如 task_struct 和 mm_struct),进行必要的初始化然后再使用。 内核的数据结构缓冲池: 这种方法本质上是一种内存池,也即“缓冲池”,专门用来存放和重用数据结构的内存。 Slab 分配器(Slab Allocator)即是实现这种内存池概念的机制之一。通过使用 slab 分配器,系统能有效减少重复的内存分配和释放操作,提高运行效率,并减少内存碎片化的问题。 它的原理是将内存按照大小分成不同的块,然后将这些块以页的形式进行分配和管理。当需要分配内存时,slab 分配器会从对应大小的块中选择一个可用的块分配给程序,当内存不需要时,这块内存又会被返回到对应的块中以便后续重复使用,从而降低了内存的分配和释放开销。这种方式可以提高内存分配的性能,减少内存碎片化 sum: 缓冲区在这个背景下的概念是指一块预分配的内存,用来存储和重复利用特定类型的数据结构。这种做法可以显著减少频繁的内存分配和释放所带来的开销,提高系统性能,这也是 slab 分配器背后的核心思想。 slab 分配器:根据合适的内存拿,不要就放回去 我们 printf 一定是把数据写入缓冲区中,合适的时候,在进行刷新 这个缓冲区绝对不在哪里? 绝对不在内核里,在用户空间,要不然一定会被刷新 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2301_80171004/article/details/140236406
-
进程组 概念 进程组就是一个或多个进程的集合。 一个进程组可以包含多个进程。 下面我们通过一句简单的命令行来展示: 为什么会有进程组? 批量操作:进程组允许将多个进程组织在一起,形成一个逻辑上的整体。当需要对多个进程执行相同的操作时,可以通过进程组进行操作实现,不用对每一个进程执行相同的操作,这样大大提高执行效率。 任务控制:在Linux操作系统中,进程组与作业控制紧密联系。用户可以通过作业控制指令来管理进程组中的进程,从而实现任务的启动、暂停、恢复、停止等功能。 功能联系:进程组中的进程通常在功能上都有相近的联系,它们协同工作完成特定任务。通过进程组可以快速的管理和这些具有共同目标的进程。 如果只有一个进程,是否有进程组? 组长进程 每一个进程组都有一个组长进程,这个进程的PID与进程组ID一样。 作用:进程组组长可以创建一个进程组 生命周期:从进程组创建存在到其中一个最后进程离开为止。 会话 概念 由多个进程组组成的集合,称为会话(session ID)。 它提供了一个运行环境和资源共享的上下文,包含了一组相关的进程,这些进程具有共同的会话标识符(SID)。 像我们通过Xshell打开的一个会话页面,就是一个会话。 我们可以通过命令查看已打开的会话: ls /dev/pts/ 1 setsid() setsid() 函数在 Unix 和类 Unix 系统中用于创建一个新的会话(session),并使调用该函数的进程成为新会话的领头进程(session leader)。这通常与创建守护进程(daemon processes)相关,因为守护进程需要独立于任何控制终端运行。 但setsid()被执行时: 创建新的会话:如果调用 setsid() 的进程不是进程组的领头进程,则该函数会创建一个新的会话,并使调用进程成为该会话的领头进程。新会话的会话ID(SID)是该进程的PID。 使调用进程脱离控制终端:如果调用 setsid() 的进程之前有一个控制终端,那么调用之后,该进程将不再具有控制终端。这意味着该进程不再是任何终端进程组的成员,也不再与任何终端相关联。 使调用进程成为新进程组的领头进程:调用 setsid() 会导致调用进程成为一个新进程组的领头进程,该进程组的ID也是该进程的PID。 注意: 如果这个进程是进程组的组长,那么将会创建会话失败;为了避免这种情况,可以在子进程里面执行该语句,同时让父进程终止;这样子进程会形成一个孤儿进程,进程ID一定是新分配的,就不会出现错误的情况了。 作业控制 作业是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含一个进程,也可以包含多个进程,进程之间互相协作完成任务, 通常是一个进程管道。 Shell 分前后台来控制的不是进程而是作业 或者进程组。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell 可以同时运⾏一个前台作业和任意多个后台作业,这称为作业控制。 例如: 守护进程 在一个会话中,会有一个进程是用来创建对应的会话,这个进程与会话对应的,这个进程被称为守护进程。 守护进程(Daemon Process)或称为服务进程,是在Unix、Linux及类Unix操作系统中运行的一种特殊类型的后台进程。守护进程独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程通常在系统启动时由系统初始化脚本启动,并在系统关闭时关闭。它们没有控制终端,因此它们不能接收来自终端的输入,也不能在终端上显示输出。 主要特点 在后台运行:守护进程在后台运行,不占用任何终端。 独立于终端:守护进程与启动它的终端无关,即使启动它的终端被关闭,守护进程仍然运行。 周期性地执行某些任务:守护进程可以定期执行特定的任务,如检查系统状态、备份数据等。 响应系统事件:守护进程也可以监听系统事件,并在事件发生时执行相应的操作。 代码演示如何创建一个会话 Deamon.hpp #pragma once #include <iostream> #include <cstdlib> #include <signal.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> const char *root = "/"; // 路径 const char *dev_null = "/dev/null"; // 重定向到哪里 void Deamon(bool ischdir, bool isclose) { // 1. 忽略可能引起程序异常退出的信号 signal(SIGCHLD, SIG_IGN); // 忽略到子进程创建的信号 signal(SIGPIPE, SIG_IGN); // 忽略到管道信号 // 2.创建子进程,关闭父进程 if (fork() > 0) exit(0); // 设置让自己成为一个新的会话, 后面的代码其实是子进程在走 setsid(); // 是否改变会话路径 if (ischdir) chdir(root); //成为守护进程,将对应的标准流进行关闭,表示到后台运行了 if (isclose) { close(0); close(1); close(2); } else//这里表示重新向到指定目录下 { // 这里一般建议就用这种 int fd = open(dev_null, O_RDWR); if (fd > 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } } } main.cc #include "Deamon.hpp" int main() { Deamon(true,false); while(true) { sleep(1); } return 0; } 将服务器守护进程化 链接:Socket编程TCP ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/m0_74068921/article/details/142344831
-
现象 开机进入ubuntu后发现没有网络,无论是在桌面顶部状态栏的快捷键 还是 系统设置中,都没有”有线网“和”无线网“的选项,”代理“的选项是有的 使用数据线连接电脑和手机,手机开启”通过usb共享网络“,还是没有任何网络 运行ifconfig和ifconfig -a都不能看到有线网(名称类似enpxxx)和无线网(wlpxxx) 运行命令sudo lshw -c network,输出类似如下,发现有两个网卡的名字(看product) *-network UNCLAIMED description: Ethernet interface product: RTL8111/8168/8211/8411 PCI Express Gigabit Ethernet Controller vendor: Realtek Semiconductor Co., Ltd. physical id: 0 bus info: pci@0000:25:00.0 logical name: enp37s0 version: 15 serial: d8:bb:c1:46:85:0e size: 1Gbit/s capacity: 1Gbit/s width: 64 bits clock: 33MHz memory:fc700000-fc703fff *-network UNCLAIMED description: Wireless interface product: Dual Band Wireless-AC 3168NGW [Stone Peak] vendor: Intel Corporation physical id: 0 bus info: pci@0000:26:00.0 logical name: wlp38s0 version: 10 serial: 18:cc:18:cc:d8:1e width: 64 bits clock: 33MHz 原因 当前安装的内核版本缺少安装linux-modules-extra-xxxx-generic 发现原因的过程(可以跳过) 我也先是在网上搜索”ubuntu 网络消失“,找到的解决方案基本都是重置NetworkManager(或者类似)的操作,但是我试了都没有用(不是无脑尝试,而是看了他们的文章后觉得可行、而且重置操作不会有啥伤害) 跟着那些教程的操作中会有使用命令ifconfig和lshw来查看当前网卡的信息。突破口就在lshw,我的输出包含”network UNCLAIMED“,去网上一查,这个回答说是缺少安装linux-modules-extra-xxxx-generic。然后我的ubuntu目前用的内核版本是6.8.0-36,再看我已经安装的内核中,果然没有安装linux-modules-extra-6.8.0-36-generic 解决方法 安装缺少安装的linux-modules-extra-xxxx-generic,其中xxxx是当前使用的内核版本(可以通过命令uname查看) 但考虑到此时ubuntu不能链接任何网络,因此有如下两个方法解决 方法一:通过命令dpkg -l|grep linux-modules,查看ubuntu是否安装了其他版本的内核 && 安装了对应的linux-modules-extra-xxxx-generic。重启电脑,在grub界面中选择“Advanced options for ubuntu" ,然后选择前面看好的内核版本,就可以正常进入ubuntu,而且有网络。此时,通过apt正常安装即可 方法二:使用其他可以联网的设备,到https://pkgs.org/上,搜索linux-modules-extra,然后根据自己的linux发行版本以及内核下载.deb文件(但是我用的是ubuntu24.04、内核版本6.8.0-36,找不到对应的包),然后拷贝到出问题的电脑上,使用命令sudo dpkg -i linux-modules-extra-xxxx-generic.deb即可 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/OTZ_2333/article/details/140062792
-
1. 清理/var/log/目录 先查看各文件夹大小 cd /var/log/ sudo du -h -d 1 1 2 rm -rf 大且不必要保留的文件夹和压缩包如todesk 删除journal下的垃圾文件: rm -rf /var/log/journal/bcd9481648344a07b557c093f4dfe5ae/ 1 2. 清理~/.cache目录 删除目录下不必要文件/夹 cd ~/.cache sudo du -h -d 1 rm -rf 大且不必要保留的软件临时缓存如pip/vscodetool等 1 2 3 3. 清理升级缓存和无用包 不推荐执行sudo apt-get autoremove sudo apt-get autoclean # 删除旧版本软件缓存 sudo apt-get clean # 删除系统内所有软件缓存 1 2 4. 清理conda缓存和不使用的包等 不推荐执行conda clean -a,会删除索引缓存、锁定文件、未使用过的包和tar包 conda clean --packages # 删除从不使用的包 conda clean --tarballs # 删除tar包 1 2 5. 删除snap软件 注意:这一条方法有争议,评论区有人反馈会删除一些重要依赖,待求证,但谨慎使用!!! snap是一款开源的压缩软件, 不是一个必须要用到的软件, 后续要用到再装, 清理后多了6G左右空间 sudo apt autoremove --purge snapd 1 清理前: df -h 清理后: df -h 6. 删除多余内核 这一步少执行, 防止误删, 风险很大 而且我测试了清理后空间好像也就腾出来1G左右, 空间优化很有限 内核不多的情况下别删,如果出现Ubuntu进不去的情况,可在ubuntu advanced回退内核 操作步骤: 首先通过uname -a查看内核版本 可以看到当前使用的ubuntu版本内核是5.4.0-152-generic, 那么内核版本就不能删! 查看所有内核 dpkg --get-selections | grep linux 1 删除与现在版本内核不同的内核信息, 以及deinstall未安装的版本信息 sudo dpkg -P linux-image-5.4.0-135-generic xxx(上边截图黄色的版本均可删) 1 删除后: 更新系统引导 sudo update-grub 1 7. 卸载不常用的软件, 清空回收站等 8. 清理不用的大文件 比如存在用户目录下的bag包, tar包等, 通常都比较大. 如果已经过时, 可以清理 查找并清理大文件 find . -type f -size +1G // 查找当前目录下大于1G的文件 find /xxx/ -type f -size +100M // 查找xxx目录下大于100M的文件 rm -rf dirName // 强制删除目录或文件 1 2 3 # 查找当前目录下大于1G的文件并挨个删除 # 慎用, 除非确认大文件确不需要 find . -type f -size +1G | xargs rm # 慎用! 1 2 3 9. 清理docker镜像/容器空间 首先通过命令查看有哪些镜像 docker images 1 删除不需要的镜像 docker rmi image_id # 或者 docker rmi image_repo:tag 1 2 3 清理无悬挂/无标签的镜像(过程镜像) docker image prune 1 同样的,查看并删除不需要的容器 docker ps -a docker rm -f container_id 1 2 可通过如下命令查看所有镜像和容器的大小 docker system df -v 1 如果有挂载盘,可修改docker默认存储目录(一般是/var/lib/docker)到挂载盘,减小默认磁盘的空间占用,这种方法博主暂时还没试,谨慎使用,参考文章[4]和[5] ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/baidu_35692628/article/details/136519924
-
一、进程状态 1.1 操作系统学科(运行、阻塞、挂起) 为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态 大多数操作系统都遵循以下原则 1.1.1 运行状态 因为有一个调度器需要确保CPU的资源被合理使用,所以需要维护一个运行队列,他将进程的task_struct结构体连接起来,而被链接起来的进程就会按顺序被调度器调度,此时处于运行队列的这些进程就处于运行态,这说明运行态并不指的是正常运行的进程,而是处在运行队列中并且随时可以被调度的进程! 1.1.2 并发执行和进程切换 调度器将进程放到CPU上去运行,并不代表必须要将进程全部运行完才会被放下来!!因为 (1)进程当中可能会存在一些死循环的进程 (2)调度器要尽量保证公平性,不能让一个进程占用CPU太长时间。 ——>那么什么时候应该把这个进程放下来,就要取决于时间片,当一个进程在超过时间片还没有结束的时候,就要把他放下去然后重新放在新的运行队列的位置。 CPU运行速度是很快的,所以其实我们人所能感受到的,所以在一个时间段内必然所有的进程都会被执行,称之为并发执行。 而大量地把进程从CPU上拿上来在放下去的这个过程,称之为进程切换! 1.1.3 阻塞状态 操作系统管理硬件的过程也是需要先描述再组织,因此不同的硬件设备都需要维护一个阻塞队列,当该硬件没有准备好的时间,该进程只能在阻塞队列中等待。 比如说scanf函数从键盘获取数据,但是如果我们一直不输入的话,这个进程就会被阻塞!! 1.1.4 挂起状态 当操作系统的内部资源严重不足的时候,需要在保证正常运行的前提下想办法把一些内存里的资源置换到磁盘中来节省空间,我们将被置换的进程所处的状态叫做挂起状态。 (1)一般来说,导致内存资源不足的原因是因为存在大量处在阻塞队列的进程 ,所以我们要办法将一些资源置换到磁盘中,但是为了不影响阻塞队列的管理,所以大多数情况下并不止直接将task_struct结构体置换过去,而是将该进程的数据和代码先置换过去,而当执行到该进程的时候,再通过某种方式将其数据和代码置换回来。 (2)其实在我们的电脑中存在一个交换分区,该分区就是专门用来置换一些导致内存溢出的资源 1、挂起状态就是PCB在排队,但是他对应的代码和数据被暂时移到外设中,节省内存空间 。 2、一个进程是否被挂起并不需要让你知道,就跟你把钱存银行里一样,你并不知道自己的钱是被干什么用了,银行并不会告诉你,只是你想要的时候他能及时给到你就好!! 扩展知识:我们的电脑现在大多数使用的都是SSD固态硬盘,磁盘一般只有大公司的后端在使用,虽然比较慢但是便宜且容量更大。 1.2 Linux内核管理进程状态方法 下面的状态在kernel源代码里定义 /* * The task state array is a strange "bitmap" of * reasons to sleep. Thus "running" is zero, and * you can test for combinations of others with * simple bit tests. */ static const char * const task_state_array[] = { "R (running)", /* 0 */ "S (sleeping)", /* 1 */ "D (disk sleep)", /* 2 */ "T (stopped)", /* 4 */ "t (tracing stop)", /* 8 */ "X (dead)", /* 16 */ "Z (zombie)", /* 32 */ }; 1.2.1 进程状态查看 ps aux / ps axj 命令 1.2.2 R状态 R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列中。 1.2.3 S状态 S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。 ——>其实就相当于是阻塞状态(因为需要跟硬件保持联系) 为了更好地观察阻塞状态,我们来举个例子: 第一段代码是只有while循环 第二段代码有printf+while循环 问题1:为什么第一种情况是R状态,而第二种情况是S状态呢??? 因为printf需要一直和显示器建立联系,所以有很大概率一直处在等待状态,因为需要等显示器准备好(CPU太快了 所以显示器缓不过来) ,如果不跟硬件建立联系那么该进程的执行速度是非常快的!! 问题2:键盘会因为用户不输入而卡着,那为什么显示器也会卡着呢?? 因为cpu的速度比外设快太多了,所以大多数的进程都在等待,另一方面我们当前的机器是云服务器,用xshell进行链接,所以还会涉及到网络的概念,自然快不了。 同理,其实我们的bash进程也是S状态,因为他在等待你输入指令,你不输入他就会卡住 1.2.4 D状态 D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。 区分:S是浅度睡眠(可以被唤醒)、D是深度睡眠 (不相应任何需求)为了能够更好地理解他们的区别,以下会讲述一个故事 比方说我们现在编译了一段代码,需要将1GB的文件写入磁盘中,内存需要跟磁盘建立联系,磁盘在被写入之前需要判断该行为是否可以被执行,比方说现在磁盘中的空间不足1GB,那么这个请求就应该被驳回,这个过程中我们的内存需要先对磁盘说:“我打算写入1GB的内容,你看看可不可以” 磁盘回复:“那你稍等一下,我看看自己的空间是否足够” 当该信息确认后,然后磁盘回复进程:“我当前空间足够,可以执行” 。然后进程才会通过其对应的代码和数据来将1GB写入磁盘。 所以这个过程有发起也有返回,内存像磁盘申请,磁盘完成后将结果返回给内存,但是这个过程是需要等待的!! 假设当前有大量的进程处于阻塞队列,此时内存不够了,因此操作系统需要杀死一部分进程来保证运行 。当系统压力很大时,依靠内存的辗转腾挪解决不了时,操作系统只能想办法杀死他认为不太重要的进程!! 内存在向磁盘发出请求的时候,在磁盘还没回复是否可行的时候该进程就被操作系统杀死了,所以磁盘想要回复的时候发现该进程不在了,所以就懵圈了。当磁盘想要回应的时候却发现那个等待自己的进程没有了,那么现在写入失败了怎么办?我是应该继续尝试呢,还是丢掉呢??此时不同的操作系统有不同的做法。 比如是在银行,某些数据丢失导致损失了几个亿!!这个时候法官 叫来了 操作系统、进程、磁盘 三个人,来这个过程应该是谁的错,第一嫌疑人是操作系统,因为操作系统杀进程了,操作系统说:“请问我是否履行了自己的职责,我是否是在比较极端的情况下去杀进程的,我能做的最大努力就是保证操作系统不挂掉!!如果我有错,那我下次再遇到这种情况??我还做不做了?就算我不杀进程,导致操作系统挂了,他数据该丢还是会丢,还会影响其他进程,这个责任又该谁负责呢??” 法官觉得操作系统说得有道理,于是又把矛头转向了第二嫌疑人磁盘,因为磁盘在写入失败的时候擅自把数据丢失了。磁盘说:“这不怪我,我就是个跑腿的,我在写入的时候就告诉他可能会失败了,所以我让他在那里等我的结果,可是他人不见了,而是丢失是因为我还有其他工作得做,如果我有错的话,那我们是不是得把磁盘所有逻辑都改了???”法官觉得磁盘说的也有代理,于是又把矛头转向了进程,此时进程扑通一声跪了下来说:“我是被杀的,我怎么能有错呢?”所以凡是存在争议很大的地方,大部分都是因为制度设置的不合理。所以法官说,你们都回去吧,我把操作系统改一改——>让一些进行在磁盘写入完毕期间,这个进程不能被任何人杀掉,其实就是不接受任何响应,但是D状态不多见因为如果有很多说明系统已经临近崩溃了!! 1.2.5 T状态 T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。 指令:kill -l 其中9是杀进程,19是暂停进程,18是重启进程 T状态存在的意义:可能是需要等待某种资源,或者是我们单纯不想让该进程运行!! 应用场景就是gdb,当程序运行起来的时候遇到了我打的一个断点,然后就停下来了,这是这个过程就可以被应用于gdb这个进程在控制被调试的这个进程!! 1.2.6 X状态 X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。 1.3 僵尸状态 比如你正在公园跑步,突然看见一个老人走了两步就倒地上了,这时候你叫了120,120发现人已经没救了,于是走了。然后你又叫了110,但是110并不会立马清理现场,因为本质查明真相的原则,可能会需要先带着法医对尸体进行检测然后再确认结果,比如说异常死亡或者是正常死亡(因为家人需要了解情况),然后才会去清理现场。 其实这段已经死亡一直到清理现场之前的这段时间,就相当于是僵尸状态。 回到进程的角度,一个进程在退出的时候并不是立即把所有的资源全部释放,而是要把当前进程的退出信息维持一段时间!!——>因为父进程需要关心子进程!! 为了观察这个现象,我们需要想办法让子进程在中途退出,所以需要exit函数 情况1:当子进程先退出 父进程还在 1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 int main() 5 { 6 pid_t id =fork(); 7 if(id==0) 8 { 9 int cnt=5; 10 while(cnt) 11 { 12 printf("child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt); 13 --cnt; 14 sleep(1); 15 } 16 exit(0); 17 } 18 else 19 { 20 while(1) 21 { 22 printf("parent,pid:%d,ppid:%d\n",getpid(),getppid()); 23 sleep(1); 24 } 25 } 26 return 0; 27 } 所以只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程保持Z状态,相关的资源尤其是task_struct结构体不能被释放!资源会一直被占用!(defunct是废弃的意思,表示当前进程已经死亡了) 问题1:为什么要存在僵尸状态?? ——>因为他要告诉关心他的进程(父进程),你交代给我的事情我办得怎么样了,所以他一定要等到父进程读取之后才能完全死亡 问题2::那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费(甚至是内存泄漏)? ——>维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话 说,Z状态一直不退出,PCB一直都要维护!!需要占用内存。 情况2:父进程先退出 子进程还在 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 int main() 5 { 6 pid_t id =fork(); 7 if(id!=0) 8 { 9 int cnt=5; 10 while(cnt) 11 { 12 printf("parent,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt); 13 --cnt; 14 sleep(1); 15 } 16 exit(0); 17 } 18 else 19 { 20 while(1) 21 { 22 printf("children,pid:%d,ppid:%d\n",getpid(),getppid()); 23 sleep(1); 24 } 25 } 26 return 0; 27 } 说明如果父进程比子进程先一步消亡,那么子进程会变成孤儿进程,他的PPID会变成1 也就是被系统进程给收养。 问题1:为什么要被领养呢?? ——>因为孤儿进程未来也会消亡,也会被释放!! 问题2:ctrl+c为什么无法中止异常进程,他的底层原理是什么?? ——>本质上是在一瞬间父进程会被bash进程回收掉!!所以子进程也在父进程退出的一瞬间被收回掉了!! 所以由于子进程的PPID不是bash进程而是系统进程,所以无法中止 问题3:子进程是bash进程的孙子进程,为什么父进程消亡后不由bash进程回收而是交由系统进程回收??? ——>因为bash做不到,因为孙子进程不是他去创建的!! 他没有这个权限,而系统进程可以做到,因为要将孤儿进程托孤给系统进程 当然不同的操作系统具体的实现方法可能也不同!! 二、Linux具体是怎么维护进程的 你可能会有这样的疑问:Linux内部维护进程主要是采用双链表的形式管理,但是由于其可能有不同的应用场景需求,所以有些时候我们也要把它放到队列、二叉树……中管理,所以为了方便这样的操作,我们的task_struct结构体里面必然需要维护各种各样类型的指针!!那么具体应该如何实现呢?? 首先并不是整个task_struct结构体链接在一起,而是通过单独创建一个node结构体来进行链接,所以其实节点都是指向该结构体中间的位置而不是头部 既然链表链接的并不是头部,那么我们通过节点的链接找到了下一个节点的某个位置,要如何去找到头部呢?? 1、将0强转成task_struct结构体的类型,其实就是假设在0位置都有一个task_struct结构体大小的内存,然后找到他的node节点并取他的地址,由于低地址是0,所以找到node节点的地址就其实就相当于知道了node在task_struct的偏移量 ——> &(task_struct*)0—>node 2、当前找到的位置然后用当前指向的位置(比如start) 减去这个偏移量,就可以找到该结构体的头部。——>start- 3、最后将这个头部的地址强转成task_struct* 就可以拿到整个PCB结构体了 ,就能访问里面的其他数据 总结: ( task_struct*)(start-&(task_struct*)0—>node)——>other 三、进程优先级 3.1 如何理解优先级 问题1:优先级vs权限 ——>权限的意义是这件事我能不能做,而优先级的意义就是对于资源的访问谁先谁后(cpu资源分配的先后顺序,就是指进程的优先权) 问题2:为什么需要有优先级 ——>因为资源是有限的,而进程是多个的,所以进程之间存在竞争关系,优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可能会改善系统性能。 问题3:操作系统是如何做的 ——>操作系统会根据自己的一套规则来尽可能地保证进程的良性竞争,如果进程长时间得不到CPU资源,那么该进程的代码就长时间无法得到推进(进程的饥饿问题),具体需要去给进程维护运行队列,让进程按顺序去执行,但为了防止某些进程运行时间过长,还会有时间片的限制(调度器在工作) 问题4:人为可以调整优先级吗? ——>优先级是可以被人为调整的,我或许可以通过调整优先级让自己的某一个进程可以在同一时间内一直被调度,但是其实Linux并不希望我们有过高的权限,所以他的调整也不是无规则地调整,是带有一定限制的!! 3.2 查看和调整优先级方法 ps –l命令则会类似输出以下几个内容: UID : 代表执行者的身份 PID : 代表这个进程的代号 PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号 PRI :代表这个进程可被执行的优先级,其值越小越早被执行 NI :代表这个进程的nice值 3.2.1 PRI和NI PRI(priotity)即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高 NI(nice)其表示进程可被执行的优先级的修正数值 PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行 所以,调整进程优先级,在Linux下,就是调整进程nice值 nice其取值范围是-20至19,一共40个级别。 3.2.2 top更改nice值 进入top后按“r”–>输入进程PID–>输入nice值 3.2.3 nice和renice改变优先级 Linux nice和renice命令:改变进程优先级 -扫盲篇_nice设置优先级为什么正数设置不了-CSDN博客 其实这方面的知识并不需要了解很深,因为大多数场景下我们并不会人为地去修改优先级 四、Linux内核的调度算法 1、需要维护两个队列让他们按顺序排队运行 问题1:为什么会有0-99的进程出现呢?? ——>因为这类进程可能是非常重要的!!也就是说无论你当前在运行什么进程,这类进程都会优先被调度(比方说电脑出故障了,发出警报,这个就是无论如何都要优先被警告) 问题2:为什么需要维护两个队列?? ——>因为需要一个队列去运行,但是在运行过程中,后来准备好的进程就会被放到另一个等待队列中,因为这样才能确保按顺序,不然比如我当前运行到后面的时候突然前面又插队了几个进程进来,我又得回头去访问!! 问题3:同等优先级的怎么办? ——>因为维护的是一个指针数组,所以比如说我当前所处的优先级是100,那么下一个优先级100的进来之后就会被链接在后面。本质上是一个开散列!! 2、需要维护一个位图,来确认位置 问题1:如何确定位置呢? ——>因为优先级有各种各样的,比方说在100的位置有2个进程,在133的位置有2个进程,但是我们并不能马上知道这几个地方有进程,而是只能通过遍历数组的方式来一个个查看。最后我们最后当数组遍历到结尾的时候才能确定队列位空 问题2:用位图大O(1) 调度算法优化 ——>只有100-139一共40个级别,我只需要用5个字节一共40个比特位来标记是否存在进程即可,这样我们就可以通过位运算的方法快速找到 队列中存在进程的位置。 最后当位图位0的时候,就说明队列位空了!! 3、需要维护两个指针 因为当运行队列运行完之后就要让等待队列进场,所以最好的方法就是维护两个指针分别指向两个队列,然后当运行队列为空的时候再交换一下指针的指向,让等待队列变成新的运行队列 五、进程重要名词概念 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行(少见,大多数配置的电脑都是只有一个cpu) 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为 并发(常见) 六、Linux内核进程切换的方法 关于进程切换,我们需要提到并发,并发是基于进程切换基于时间片轮转的调度算法 那么进程具体是如何切换的呢?? 6.1 CPU知识铺垫 问题引入 1、我们的函数返回值是局部变量,但是为什么可以被外部拿到呢?? ——>因为将数据存储在CPU寄存器中带了出去!! 比如return a会转化成move eax 10的指令 2、系统如何得知我们的进程执行到了哪句代码?? ——>因为有程序计数器pc(在PCB内部)以及eip(栈帧寄存器),他们的作用就是记录当前进程正在执行的下一行指令的地址 (其实我们选择语句、循环……的跳转都跟pc有关) 3、寄存器有很多,具体有哪些种类呢??? 通用寄存器:eax、ebx、ecx、edx……(需要什么就做什么的寄存器) 栈帧寄存器:ebp、esp、eip……(ebp和esp是维护栈顶和栈底的,而eip是存储程序计数器的值,表示着进程的下一条指令应该从哪里执行) 状态寄存器:status(记录当前CPU的一些状态) 4、寄存器扮演什么角色呢?? ——>寄存器存储的空间并不大,但是速度快,所以他的作用就是 提高效率,将进程的高频数据放入寄存器中!! 5、如果返回的是内置类型,寄存器可以做到,但如果返回的是一个特别大的对象呢?? ——>因为内置类型很小,所以默认可以被翻译成mov eax ,但如果是一个对象的话,其实本质上来说就是进行了一个拷贝构造,如果是C++11的话可能还会涉及到移动语义,所以本质上来说也是由一个个语句组成的,所以就允许使用多个寄存器来工作。 相当于就是多次mov。 6、寄存器总结 ——>(1)CPU寄存器里面保存的是进程的临时(高频)数据——>进程的上下文信息 (2)由于进程保存的是临时的数据,所以新的数据进入的时候可能会需要覆盖老的数据,因此进程的一些更重要的数据应该被放在离CPU更近的地方!!因为那样更安全更不易丢失!!(比如说重要数据肯定不适合放在通用寄存器上) 6.2 进程切换的本质 下面讲两个故事: 1、你步入大学,但是你有一个参军梦,于是你报名了并且成功被选上了,你很开心于是你就直接去军营了,但是你走之前床铺没有收拾、也没有告诉学校。 于是当你在军营的时候,其实学校并不知道有你去军营了,所以他会给你安排考试,安排宿舍……结果过了一段时间当你回校后,你发现你的宿舍早就换人了,你床铺的东西也都被丢了,然后你本来应该是大一的,却显示大三,挂了三四十门课,即将被勒令退学…… 这个时候你找到了学校说:“我又不是干坏事,而是去当兵,为什么要让我退学??” 学校:“这不怪我啊,你没有打招呼,我根本就不知道你当兵去了”…… 2、还是刚刚的你,但是这次你把自己的床铺收拾好了打包带走,然后你走之前去跟导员报告,将自己的入伍证明交予他查看,然后像学校请求保留学籍,其实就相当于把你的档案给封存了,这个时候学校知道你去当兵了,所以就不会给你安排考试,不会安排宿舍,你的学籍被保留了,你是大二上学期当的兵,你回来时候也是大二上学期…… 通过上面两个故事,我们明白了在我们离开之前一定要做好收尾,这样才能更好地回来,所以对于进程来说,进程从CPU离开的时候,也要将自己的上下文数据保存好然后带走,保存的目的是为了更好地回来(恢复) 问题引入: 问题1:进程在被切换的时候要做什么 ——> (1) 保存上下文 (2)恢复上下文 问题2:pc帮助我们记录下一条指令要运行的位置,那么要保存的数据究竟是存放在哪里呢? ——>存储在PCB中,PCB内部应该有相关的结构体,在寄存器要去执行其他进程之前将相关的数据先存在内部,然后寄存器就可以离开了,当后面寄存器回来的时候就可以帮助进程恢复之前的数据。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/weixin_51142926/article/details/141506586
-
《Linux 中的网络管理:强大而灵活的工具集》在 Linux 系统中,网络管理是一项至关重要的任务。无论是配置网络接口、设置 IP 地址、管理路由,还是监控网络性能,Linux 都提供了丰富的工具和强大的功能。本文将深入探讨 Linux 中的网络管理。一、网络配置文件Linux 中的网络配置主要通过一系列配置文件来实现。其中,最重要的文件包括:/etc/network/interfaces:在许多 Linux 发行版中,这个文件用于配置网络接口。你可以在这里指定 IP 地址、子网掩码、网关等信息。例如:收起plaintext复制auto eth0 iface eth0 inet static address 192.168.1.100 netmask 255.255.255.0 gateway 192.168.1.1/etc/resolv.conf:这个文件用于配置 DNS 服务器。你可以在这里指定一个或多个 DNS 服务器的 IP 地址。例如:收起plaintext复制nameserver 8.8.8.8 nameserver 8.8.4.4二、网络管理工具ifconfig:这是一个用于查看和配置网络接口的工具。你可以使用它来查看网络接口的状态、IP 地址、子网掩码等信息,也可以使用它来临时配置网络接口。例如:收起plaintext复制ifconfig eth0 192.168.1.101 netmask 255.255.255.0 upip:这是一个更强大的网络管理工具,它可以替代ifconfig。ip命令可以用于查看和配置网络接口、路由、邻居缓存等。例如:收起plaintext复制ip addr show eth0 ip link set eth0 up ip route add default via 192.168.1.1route:这个工具用于查看和配置路由表。你可以使用它来查看当前的路由表,也可以使用它来添加、删除或修改路由。例如:收起plaintext复制route -n route add -net 192.168.2.0/24 gw 192.168.1.2netstat:这个工具用于查看网络连接、路由表、接口统计等信息。例如:收起plaintext复制netstat -an netstat -rnmap:这是一个用于网络探测和安全审计的工具。你可以使用它来扫描网络中的主机、端口等信息。例如:收起plaintext复制nmap 192.168.1.0/24 nmap -p 80 192.168.1.100三、网络监控工具iftop:这是一个实时网络流量监控工具。它可以显示网络接口上的实时流量情况,包括上传和下载速度、连接的主机等信息。例如:收起plaintext复制iftop -i eth0nethogs:这个工具可以实时监控每个进程的网络使用情况。它可以帮助你找出哪个进程占用了大量的网络带宽。例如:收起plaintext复制nethogs eth0tcpdump:这是一个强大的网络数据包捕获工具。你可以使用它来捕获网络接口上的数据包,并进行分析。例如:收起plaintext复制tcpdump -i eth0 tcpdump -i eth0 port 80四、网络故障排除当网络出现问题时,Linux 提供了一些工具来帮助你进行故障排除。以下是一些常用的方法:检查网络接口状态:使用ifconfig或ip addr show命令查看网络接口的状态。确保接口已启用,并且有正确的 IP 地址和子网掩码。检查路由表:使用route -n或ip route show命令查看路由表。确保有正确的默认网关和其他路由。检查 DNS 配置:使用cat /etc/resolv.conf命令查看 DNS 服务器的配置。确保有正确的 DNS 服务器 IP 地址。检查网络连接:使用ping命令测试与其他主机的连接。例如,ping 8.8.8.8可以测试与 Google 的公共 DNS 服务器的连接。使用网络监控工具:使用iftop、nethogs或tcpdump等工具来监控网络流量,找出可能的问题。五、总结Linux 中的网络管理提供了丰富的工具和强大的功能,可以满足各种网络管理需求。通过了解网络配置文件、网络管理工具和网络监控工具,你可以更好地管理 Linux 系统中的网络。在进行网络管理时,一定要小心谨慎,确保不会对系统的稳定性和安全性造成影响。如果遇到问题,可以使用网络故障排除工具来找出问题的根源,并及时解决。
-
《Linux 中的 OpenSSH:强大的远程连接工具》在 Linux 系统的管理和使用中,OpenSSH 是一个不可或缺的工具。它为我们提供了安全、可靠的远程连接方式,让我们能够轻松地管理远程服务器。今天,我们就来深入了解一下 Linux 中的 OpenSSH。一、OpenSSH 简介OpenSSH 是一款开源的 SSH(Secure Shell)协议的实现,用于在不安全的网络中提供安全的远程登录和其他网络服务。它使用加密技术来保护通信的安全性,防止数据被窃取或篡改。二、安装 OpenSSH在大多数 Linux 发行版中,OpenSSH 通常是默认安装的。如果你的系统中没有安装 OpenSSH,可以使用包管理器进行安装。例如,在 Ubuntu 系统中,可以使用以下命令安装 OpenSSH:sudo apt-get install openssh-server在安装完成后,OpenSSH 服务会自动启动。三、使用 OpenSSH 进行远程连接远程登录 使用 SSH 客户端可以远程登录到其他 Linux 服务器。在命令行中输入以下命令:ssh username@remote_host其中,username是远程服务器上的用户名,remote_host是远程服务器的 IP 地址或域名。在第一次连接时,系统会提示你确认远程服务器的指纹,输入yes确认后,就可以输入密码进行登录。文件传输 OpenSSH 还提供了文件传输功能,可以使用scp命令在本地和远程服务器之间传输文件。例如,将本地文件local_file传输到远程服务器的remote_directory目录下,可以使用以下命令:scp local_file username@remote_host:remote_directory如果要从远程服务器下载文件,可以使用以下命令:scp username@remote_host:remote_file local_directory端口转发 OpenSSH 还可以进行端口转发,实现通过本地端口访问远程服务器上的服务。例如,将远程服务器上的 80 端口转发到本地的 8080 端口,可以使用以下命令:ssh -L 8080:localhost:80 username@remote_host这样,就可以在本地浏览器中通过访问http://localhost:8080来访问远程服务器上的 Web 服务。四、配置 OpenSSHOpenSSH 可以通过配置文件进行定制化配置。主要的配置文件是/etc/ssh/sshd_config,用于配置 SSH 服务器的行为。以下是一些常见的配置选项:端口号 可以修改默认的 SSH 端口号,增强安全性。在配置文件中找到Port选项,将其修改为你想要的端口号。密码认证 可以禁用密码认证,使用密钥认证来提高安全性。在配置文件中找到PasswordAuthentication选项,将其设置为no。密钥认证 使用密钥认证可以避免输入密码,提高登录的便利性和安全性。首先,在本地生成密钥对,可以使用以下命令:ssh-keygen然后,将公钥复制到远程服务器上,可以使用以下命令:ssh-copy-id username@remote_host五、安全注意事项定期更新 OpenSSH 为了确保安全性,建议定期更新 OpenSSH 到最新版本。可以使用包管理器进行更新。加强密码安全 如果使用密码认证,建议使用强密码,并定期更换密码。限制访问 可以通过配置防火墙或使用AllowUsers和DenyUsers选项来限制哪些用户可以远程登录到服务器。六、总结OpenSSH 是 Linux 系统中非常强大的远程连接工具,它提供了安全、可靠的远程登录和文件传输功能。通过合理配置 OpenSSH,可以提高服务器的安全性和管理效率。在使用 OpenSSH 时,一定要注意安全问题,确保服务器的安全稳定运行。
上滑加载中