-
我看版本好低,就跑了一个 do-release-upgrade 没想到成功把系统升级崩了。。。中间卡在该死的snap的firefox上面了,一直不动我就给直接关了,没想到到就再也连不上了。。。。这个傻逼snap真的是服了。。。
-
Linux中Vi编辑器的高级用法详解Vi是Linux系统中一款功能强大的文本编辑器,尤其在没有图形界面的服务器环境中,它几乎是唯一的选择。尽管界面简单,但Vi凭借丰富的命令和高效的编辑能力,赢得了程序员们的青睐。本文将详细介绍Vi编辑器的高级用法,包括常用命令、分屏操作、代码折叠、高效编辑技巧等,并附上详细的代码示例,帮助读者快速掌握Vi的高级技巧。一、Vi简介及基本工作模式Vi(Visual Interface)是Linux中最经典的文本编辑器之一,其设计思想是让程序员的手指始终保持在键盘的核心区域,通过命令对文件进行快速编辑。Vi具有三种基本工作模式:命令模式、末行模式和编辑模式。命令模式:打开文件后首先进入命令模式,这是使用Vi的入口。在命令模式下,可以执行定位、翻页、复制、粘贴、删除等常规编辑操作。末行模式:执行保存、退出等操作需要进入末行模式。末行模式是Vi的出口,通过输入命令完成特定操作。编辑模式:在编辑模式下,可以正常地输入和编辑文字。二、Vi的高级用法1. 移动光标与选择文本Vi之所以高效,关键在于能够快速定位到要编辑的代码行。以下是几种常用的移动光标和选择文本的方法:基本移动:使用方向键(h、j、k、l)进行上、下、左、右移动。行内移动:0 移动到行首,^ 移动到行首的非空白字符,$ 移动到行尾。行数移动:gg 移动到文件开头,G 移动到文件末尾,nG 移动到第n行。屏幕移动:Ctrl-F 向前翻页,Ctrl-B 向后翻页,Ctrl-D 向下半页,Ctrl-U 向上半页。可视模式:在选择文本之前,需要先进入可视模式。Vi提供了三种可视模式:字符可视模式、行可视模式和块可视模式。字符可视模式:按v进入,使用方向键选择要复制的文本。行可视模式:按V(Shift+v)进入,选择要复制的行。块可视模式:按Ctrl-v进入,选择矩形块。# 进入可视模式并选择文本 v # 进入字符可视模式 V # 进入行可视模式 Ctrl-v # 进入块可视模式2. 编辑操作在Vi中,编辑操作主要包括删除、复制、粘贴、替换和缩排等。删除文本:x 删除光标所在字符。dw 删除从光标位置到单词末尾的字符。d0 删除从光标位置到行首的字符。d$ 删除从光标位置到行尾的字符。dd 删除当前行。ndd 从光标位置向下连续删除n行。复制文本:yy 复制当前行。nyy 从光标位置向下连续复制n行。yw 复制从光标位置到单词末尾的字符。粘贴文本:p 将缓冲区中的文本粘贴到光标所在位置。替换文本:r 替换光标所在字符。R 进入替换模式,可以连续替换多个字符,按Esc退出替换模式。# 删除和复制文本示例 dw # 删除当前单词 dd # 删除当前行 yy # 复制当前行 p # 粘贴文本3. 撤销和重复撤销:u 撤销上一次编辑操作。重复:Ctrl-r 重复上一次撤销的操作。4. 查找和替换查找:/ 进入查找模式,输入要查找的文本,按Enter开始查找。n 查找下一个匹配项。N 查找上一个匹配项。替换::s/old/new/g 在当前行中全局替换old为new。:1,$s/old/new/g 在整个文件中全局替换old为new。# 查找和替换示例 /pattern # 查找pattern :s/foo/bar/g # 在当前行中替换foo为bar :1,$s/foo/bar/g # 在整个文件中替换foo为bar5. 设置快捷键和别名快捷键:可以在/.vimrc文件中定义快捷键,提高编辑效率。例如,将":map :w"添加到/.vimrc文件中,这样按下F2键就会保存当前文件。# 在~/.vimrc文件中定义快捷键 :map <F2> :w<CR>别名:可以使用缩写来简化输入。例如,在Vi的命令模式下输入":ab jlu Jilin University",之后输入"jlu "就会被自动替换为"Jilin University"。# 定义别名 :ab jlu Jilin University6. 分屏操作Vi支持水平分屏和垂直分屏,可以同时编辑多个文件。水平分屏::sp 文件名 在当前窗口的下方分割一个新窗口。垂直分屏::vsp 文件名 在当前窗口的右侧分割一个新窗口。切换窗口:Ctrl-w + 方向键 切换窗口。# 分屏操作示例 :sp file2.txt # 水平分屏打开file2.txt :vsp file3.txt # 垂直分屏打开file3.txt Ctrl-w h # 切换到左窗口 Ctrl-w j # 切换到下窗口 Ctrl-w k # 切换到上窗口 Ctrl-w l # 切换到右窗口7. 代码折叠Vi支持代码折叠,可以方便地隐藏和显示代码块,提高代码的可读性。折叠方式:使用:set fdm=***命令设置折叠方式,常见的折叠方式有manual、indent、expr、syntax、diff和marker。折叠命令:zc 折叠当前代码块。zC 折叠当前范围内的所有嵌套代码块。zo 打开当前折叠的代码块。zm 折叠更多代码块。zr 打开更多代码块。zE 删除所有折叠。# 设置折叠方式并折叠代码 :set fdm=marker zc # 折叠当前代码块 zo # 打开当前折叠的代码块8. 高效编辑技巧标记和跳转:使用m命令添加标记,使用''或'`命令跳转到标记位置。快速匹配:使用*或#命令查找当前单词的下一个或上一个匹配项。文本对象:使用vi{}、va{}、vi()等命令选中括号内的内容。多行缩进:在可视模式下,使用>或<命令增加或减少缩进。# 标记和跳转示例 ma # 在当前位置添加标记a 'a # 跳转到标记a的位置 # 快速匹配示例 * # 查找当前单词的下一个匹配项 # # 查找当前单词的上一个匹配项 # 文本对象示例 vi{ # 选中{}内的内容(不包括{}) va{ # 选中{}内的内容(包括{})三、总结Vi作为一款功能强大的文本编辑器,在Linux系统中具有不可替代的地位。通过掌握Vi的高级用法,可以大大提高文本编辑的效率。本文详细介绍了Vi的移动光标、选择文本、编辑操作、撤销重复、查找替换、设置快捷键和别名、分屏操作、代码折叠以及高效编辑技巧等高级用法,并附上了详细的代码示例。希望读者通过本文的学习,能够熟练掌握Vi的高级技巧,并在实际工作中灵活运用。
-
我喜爱一切不彻底的事物。琥珀里的时间,微暗的火,一生都在半途而废,一生都怀抱热望。夹竹桃掉落在青草上,是刚刚醒来的风车;静止多年的水,轻轻晃动成冰。我喜爱你忽然捂住我喋喋不休的口,教我沉默。——张定浩前言 时光如流水,来去匆匆,有的时候你并不知道你是虚拟的环境中还是在真实的环境中,一切只是内核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。
-
我喜爱一切不彻底的事物。琥珀里的时间,微暗的火,一生都在半途而废,一生都怀抱热望。夹竹桃掉落在青草上,是刚刚醒来的风车;静止多年的水,轻轻晃动成冰。我喜爱你忽然捂住我喋喋不休的口,教我沉默。——张定浩前言 时光如流水,来去匆匆,有的时候你并不知道你是虚拟的环境中还是在真实的环境中,一切只是内核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
推荐直播
-
TinyEngine低代码引擎系列.第1讲——低代码浪潮之下,带你走进TinyEngine
2024/11/11 周一 16:00-18:00
李老师 高级前端开发工程师
低代码浪潮之下,带你走进TinyEngine。李旭宏老师将从低代码的发展趋势、TinyEngine的项目介绍,三方物料组件的使用、跨技术栈的使用、源码生成能力的差异性对比等多个方面带大家对TinyEngine低代码引擎有一个更清晰的认知和了解。
即将直播 -
0代码智能构建AI Agent——华为云AI原生应用引擎的架构与实践
2024/11/13 周三 16:30-18:00
苏秦 华为云aPaaS DTSE技术布道师
大模型及生成式AI对应用和软件产业带来了哪些影响?从企业场景及应用开发视角,面向AI原生应用需要什么样的工具及平台能力?企业要如何选好、用好、管好大模型,使能AI原生应用快速创新?本期直播,华为云aPaaS DTSE技术布道师苏秦将基于华为云自身实践出发,深入浅出地介绍华为云AI原生应用引擎,通过分钟级智能生成Agent应用的方式帮助企业完成从传统应用到智能应用的竞争力转型,使能千行万业智能应用创新。
去报名
热门标签