-
一、认识硬件——磁盘 1.1 物理构成 磁盘是唯一的一个机械设备,也是一个外设! 以前的老式电脑上装的就是机械磁盘,现在由于用户对使用计算机的速度要求越来越高,现在我们普通人使用的电脑基本上都是用的SSD固态硬盘,SSD固态硬盘并没有像机械磁盘那样的机械运动,读写速度更快,且具有体积小、低功耗、耐用性好、无噪音等特点!且未来还有很大的研究空间!所以在桌面领域几乎取代了机械磁盘! 但是企业级存储更倾向于使用机械硬盘,由于其成本低、容量大的特点更有利于进行大规模的数据存储,所以他其实很难被淘汰! 1、写入磁盘工作原理:二进制序列会通过磁头的充放电(任何硬件都只认识二进制序列,因为本质上是用线连接的),将数据写到盘片上。 2、 一些特点: (1)我们的计算机内部的信息流动是以电子或者光电信号的形式传递(非常快),而磁盘是机械运动相比之下速度很慢!! (2)盘片高速旋转,磁头左右转动,磁头是一面一个且和盘面不接触! (3)磁盘在设计的时候必须保证无尘环境且密封完好,因为有灰尘的话可能会导致盘面刮花造成数据丢失。 (4)内存是掉电易失存储介质,盘片是永久性存储介质。 3、注:磁盘是有寿命的,大公司磁盘快报废的时候并不敢直接把磁盘给丢掉,因为里面存储了大量的用户数据(磁盘密封性好且不易被销毁),所以相关的安全部门对大公司的磁盘销毁工作时有严格的要求的! 1.2 存储构成 磁头的左右摆动——>定位磁道(柱面) 磁头不动的时候,盘片的旋转——>定位扇区 所以磁盘被访问的最基本单位是扇区 ——> 512字节/4KB ——>因此我们可以把磁盘看做由无数个扇区构成的存储介质 ! ——>所以我们要把数据存到磁盘,首先就是定位一个扇区:(1)先找到哪一面(定位磁头)(2)哪一个磁道(3)哪一个扇区 1.3 逻辑抽象 我们把假设把磁带摊开,从逻辑上我们就可以把他看做是线性的!我们就可以用一个数组把扇区组织起来,每个下标对应着一个扇区(我们把逻辑扇区地址叫做LBA地址)! 问题1:那么我们如何通过下标找到我们要写入的扇区呢?? 问题2: 为什么扇区大小不均匀的但是LBA地址是均匀?? ——>因为磁道是从中间向外辐射的,所以里面的磁道有多少个扇区,外面的磁道就有多少个扇区,只不过里面磁道的扇区会小一点(扇区大小不均匀) 但其实可以通过调整密度来变得均匀(里面的01序列稠密一点,外面的稀疏一点) 现在的磁盘其实也是可以做到让外面扇区较大的多存点数据,但是这样的话相关的算法就得更改!! 1.4 回归硬件 所以我们究竟是如何和硬件交互的呢??? 不仅CPU有寄存器,其他外设也有寄存器,包括磁盘!! CPU向磁盘发送IO方向的请求时——> 1、控制寄存器(告诉磁盘是打算读还是打算写) 2、数据寄存器(告诉磁盘要写入哪些数据) 3、地址寄存器(告诉磁盘要写的LBA地址,磁盘自己通过CHS寻址) 4、结果寄存器(CPU获取IO请求是否成功的状态,比如可能空间不足写入失败) 二、文件系统 通过逻辑抽象,我们可以把对扇区的地址抽象成线性的LBA地址,但是具体磁盘有多大呢?已经用了多少扇区?哪些扇区还没被使用?哪些扇区存的是属性?哪些扇区存的是内容?要往哪个扇区去写入??——>诸如以上问题,注定了我们的操作系统必须想办法把磁盘空间组织起来!! 首先是磁盘分区,我们可以把一个磁盘分成多个分区。 而每个分区,都可以用以下的区域来表示 Boot Block: 是文件系统中的一个特殊块,位于文件系统的起始位置。 它包含了引导加载程序(Boot Loader)所需的信息,用于引导操作系统的启动过程。 Block group:每个分区被分成了一个个的block,该block的大小是由格式化确定的,不可被修改,每个block由可以细分为6个区域。 Linux中,文件的属性和内容是分开存储的!! 2.1 inode inode:存储的是单个文件的全部属性(128字节) ,一般而言,一个文件只有一个inode! Linux系统里标记文件的唯一标识用的是inode,且文件的属性中不包含文件的名称!! 通过ls -li 可以观察到文件的inode编号 其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息 2.2 Data Block Data Block:存文件内容的区域,以块的形式呈现,常见的是4KB大小,一般而言一个块只有自己的数据! 一个文件只有一个inode,但如果是个大文件可能会有很多个块。所以在inode结构体内部会有一个block数组,存储的是数据块的块号。 问题:那难道是文件内容需要多少个数据块,block数组就需要有多大么??? ——>并不是的,block数组内部除了一部分是直接索引(存储文件块号,可直接找到文件内容),还有一小部分是二级索引(该块号不会存储文件内容而是继续存储文件的块号) 2.3 Bitmap 我怎么知道,哪些块被使用过,哪些块没被使用过呢??? 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用 inode那么多,我怎么知道要给该文件分配哪个呢?? inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。 问题1: 为什么我们下载一个文件需要很久,但是删除的时候却很快呢?? ——>删除一个文件的时候,并不会把块(文件内容)清空,而仅仅只是把对应比特标志位给修改了,表明该块是空闲可用的。 问题2:如果我们想恢复一个被删除的文件,要怎么办呢?? ——> 如果我们不小心误删的一个文件,如果这个文件很重要,最好的办法就是什么都不做然后找专业的人去恢复(一般来说恢复其实就是得找到该文件的inode编号,比如通过Linux的日志信息找到被删文件的inode,但是具体怎么恢复得依靠一些专业的东西!),因为虽然内容还在,但是他的位图信息表明是可以被使用的,所以你如果乱操作可能会导致文件内容被覆盖。 2.4 GDT和超级块 GDT(Group Descriptor Table):块组描述符,描述块组属性信息(该block组的分配信息和使用情况)。 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量, 未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个 文件系统结构就被破坏了 (整个分区的相关信息) 因为超级快是在组里面存在的,但是记录了整个分区的信息,所以并不需要每个组都有, 其实理论上来说有一个就够了(太多会影响速度),但是由于他非常重要,一旦损坏就会造成文件系统崩溃,所以一般来说一个分区里面会有个别组有超级快,这样即使一个超级块崩了,也可以通过其他超级块区对文件系统做修正!! 问题:超级块决定了文件系统能否正常运行,块又那么多,操作系统是如何定位超级块的呢?? ——> 魔数,一个随机值,在超级块中的位置是确定的,只要我们操作系统去读该块的规定偏移量位置看看是否存在魔数,就可以确定该块是不是超级块!! 2.5 格式化 每个分区在使用之前,就必须提前将部分文件系统的属性信息提前设置到对应的分区中,方便我们后续使用这个分区或者分组!! 对磁盘做格式化可以帮助我们让磁盘恢复到完全没有被使用过的状态。 三、对目录的理解 3.1 新建和删除文件,系统做了什么? 1、新建文件:系统会在某个路径下去创建,路径帮助我们确定在哪一个分区,读取了超级块后确定了一个block,然后通过查该block的GDT知道该分区的inode还有很多没有被使用,于是到inodebitmap位图结构里找到了没有用过的inode,然后分配给该文件,分配好之后把自己的属性往里面填,这样就算新建成功了!! 如果还打算写入的话,先确认一下要写入内容的大小,确定一下需要几个块,然后到blockbitmap里面找到没被使用的块,把块号填到inode结构体里面的block数组里,然后再把文件内容写进这些块里面 2、删除文件:先找到分区,再找到inode,然后把对应inodebitmap和blockbitmap的位置置0。其实文件还在,只不过对应的块可以被覆盖!! 我们会发现,无论是什么操作,最重要的就是如何去找到文件的inode,可以我们如何知道一个文件的inode呢??其实使用者压根没关心过inode,我们只关心文件名!! 3.2 目录文件 目录也是文件,也有自己的inode ,也是有内容+属性构成! 重点:目录文件的文件内容放的是文件的文件名+对应文件的inode映射关系!! 问题1:为什么一个目录下不能有同名文件?? ——>因为文件名和inode是key和value的关系,key值必须唯一。 问题2:为什么没有w无法创建文件?? ——>w意味着无法写,所以我们就无法将文件名和inode的映射关系写进去,因此无法创建文件。 问题3:为什么没r无法查看文件?? ——>r意味着无法读,所以我们也看不到文件名和inode的映射关系,找不到inode就找不到文件。 问题4:为什么没有x无法进入目录?? ——>x意味着没有操作权限,要进入一个目录必须cd目录名,并且将当前目录名更新到环境变量PWD中,所以无法进行该操作的话我们就无法进入。 问题5:通过读取目录文件内容可以找到文件的inode,那么目录的inode如何查找呢?? ——>你当前的目录其实也是别的目录的子目录,所以就得递归往上找,一直找到根目录,然后再从根目录往下找回来,读取每个目录的数据块,直到找到该目录的inode.所以访问文件必须带路径!! 3.3 dentry缓存(扩展) 找目录的inode要递归向上找到根目录,然后再找回来,难度不会很慢么??确实会的,所以Linux提供了dentry缓存,将常用文件的inode信息缓存起来!! dentry缓存,简称dcache,是Linux为了提高目录项对象的处理效率而设计的。它是一个slab cache,保存在全局变量dentry_cache中,用于保存目录项的缓存。dentry结构是一种含有指向父节点和子节点指针的双向结构,多个这样的双向结构构成一个内存里面的树状结构,也就是文件系统的目录结构在内存中的缓存了。 linux 内核 目录项高速缓存 dentry cache 简介-CSDN博客 四、软硬链接 建立方式: 4.1 如何理解硬链接? 硬链接不是一个独立的文件,因为他没有独立的inode!! ——>所谓的建立硬链接,本质上就是在特定目录的数据块中新增文件名和指向的文件inode编号的映射关系(有点像起别名的感觉,可以让多个文件名指向一个inode)。 问题1:为什么dir的引用计数是2?? ——>因为 . 是dir的一个硬链接 问题2:为什么dir的上级目录引用计数是3? ——>因为 dir中的 ..是上级目录的一个硬链接 总结: (1)无论他的引用计数是多少,-2就是他当前子目录的数目(比如21,那么他的子目录就是19) (2)所以删除文件并不是直接把该文件的位图清空,本质上是先把目录里该文件的inode映射关系去掉,然后再到inode里面把引用计数-- 引用计数为0时才是把文件的位图清空。 问题3:为什么Linux不允许对目录建立硬链接??(重点!!) ——>为了防止出现环的题,b比方说我们要找一个目录的inode,会向上索引找到根目录,再向下找回来,如果恰好这个过程中出现了 上级目录的硬链接,那么就会回退回去,造成死循环!! 问题4:可是目录内部不是有. 和 .. 的硬链接吗??(重点!!) ——> (1) . 和 .. 是操作系统创建的,他不让你创建是为了你好,担心你创建之后出现环的问题,其实. 和..按照道理也会有环的问题,但是操作系统提前规定好了 .和..不会被做搜索,这是强制规定的!所以不会有环的问题! (2)其实操作系统干嘛要多此一举搞个. 和 ..呢??不就是为了方便让用户使用相对路径么?? 由于目录文件的inode需要递归向上索引才能找到,所以我们总是需要给想要找的文件加上绝对路径,现在操作系统给我们. 和 .. ,我们就可以用相对路径了! 硬链接应用场景:通常用来做路径定位!!可以通过硬链接进行目录切换!(不常用) 4.2 如何理解软链接? 软连接是一个独立的文件,有独立的inode,也有独立的数据块 ,他的内容里面保存的是指向的文件的路径。(相当于windows的快捷方式) 应用场景:当一个可执行文件在路径深处时,我们要找到他比较麻烦,如果我们在当前路径使用,就可以在当前路径建立一个该文件的软连接。(可执行程序随便放都行,只要有软链接,较常用) 五、文件系统和内存系统的关联 5.1 硬件和内存交互的基本单位 物理内存是以4KB为基本单位的(由操作系统决定的) 物理内存交互的单位叫做页框,而磁盘交互的单位叫做页帧 问题:为什么是4KB而不是要多少给多少呢??难道我访问100字节不比访问4KB快吗?? ——>理论上是这样的,但是100字节可能在4KB的不同位置,需要访问的话还需要对磁盘做更精细的计算(磁盘速度太慢了),甚至是文件系统也需要更精细的方法,操作系统文:你能保证你后几秒不用上下文数据吗??反正4kB和100字节效率差不是很多,我都给你拿过来,说不定你用得上呢!(根据局部性原理:程序倾向于访问近期或者近邻的数据,这是计算机性能优化的重要原则,合理运用可以整体提速!) 总结:(1)硬件:减少IO的次数——减少访问外设的次数 (2)软件:基于局部性原理而产生的预加载——整体提速 5.2 操作系统如何管理内存 虚拟地址是操作系统提供的,我们用户只能看到纯的虚拟地址,具体这个虚拟地址具体映射到物理内存的哪个位置,我们并不关心也并不知道,这是操作系统帮我们决定好的,所以操作系统必然可以看得到物理地址!! 那么操作系统要如何去管理我们的内存呢?? Page的配置不能太大,因为这样的话会占据大量的空间。 flag:检查当前page的状态(比如说当前检测到我们要访问的内容并没有被加载到内存中,所以这个时候就会发生缺页中断) count:引用计数(可以通过引用计数知道当前有多少个进程在共享我这个page,当有进程需要去修改内部的数据的时候,就会发生写时拷贝) 所有申请内存的工作,本质上都是访问Page数组!!(根据一个随机地址判断这个地址属于哪个Page的方法:1KB=2^10字节 所以4KB=2^12字节 恰好在16进制地址中就是后三位数字,所以我们任何一个地址只要 & 0xFFFF F000 ,就可以找到该地址对应的Page) 5.3 文件页缓冲区 在Linux中,我们每一个进程打开的每一个文件都具有自己的inod和文件页缓冲区!! 在我们的file结构体狸猫有一个address_spqce结构体,里面又有一个page_tree,里面又有一个 radix_tree_node节点 page_tree这个结构有点类似于B树。他不断延伸最后会找到我们想要寻找的Page结构体! 内存管理,并不是直接让那个我们去访问page数组,而是通过一些配套的算法。 5.4 字典树的理解 以上是一颗相对简单的字典树(组合式的KV模型),比方说我们想要找cba,我们就可以按照这颗树索引下去,找到我们想要找到的内容(某个对象数据) 为什么操作系统要用字典树这个结构来管理我们的page呢?? ——>因为文件的内容按照4KB是有偏移量的。(虽然我们的块是4KB大小,但是我们不一定是在开头去读取或写入,而操作系统通过字典树这种组合式的KV结构来帮助我们定位具体的位置) 5.5 串联进程管理、文件管理、内存管理 首先我们创建了一个进程,创建了一个PCB结构体,然后还会顺便创建一张文件描述符表,我们调用C接口去打开写入文件的时候,该文件会在该文件描述符表中分配一个位置,然后创建一个file结构体,里面分配了inode,但是文件的内容会先写入在C层的缓冲区, 当满足刷新策略的时候,再通过一些方式将其刷新到我们的page中(内核缓冲区)。然后最后再刷新到磁盘中。所以这个过程数据被拷贝了3次 当我们的进程将数据交给内存后,其实他就不管了,所以我们的操作系统必须关心内存要如何刷新到磁盘上,且可能同一时间有大量的IO请求,因此我们的操作系统也要关心先执行哪个请求。所以这就涉及到IO子系统(操作系统和驱动做交互的系统) 我们的IO请求会将相关的一些需求和配置写到一个request结构体里,然后再由操作系统做管理 ——>因为我们的不同的IO请求可能是向不同的扇区写入,所以我们肯定希望比较接近的扇区在一起被写入,这样减少磁头定位能够提高整体效率,所以我们会用一个队列把request管理起来,然后设计一些IO排序和IO合并算法,来给IO请求整体提速!! 在我们开机的时候,因为物理内存经常需要跟磁盘做交互,所以会提前把一些访问物理内存所需要的区域会被预先加载进去,尤其是文件系统的相关功能。 ——>说明操作系统真的帮助我们做了很多事情!! 5.6 slab分配器和伙伴系统(扩展) 从我们创建进程开始,或者是申请文件,我们就会有各种各样的结构体不断地被创建和释放 所以当我们的内核数据结构想要释放的时候,操作系统并不会直接释放,而是会把一些高频使用的内核数据结构体通过某些机制暂时缓存起来,当你下次需要的时候你就不需要跟内存模块做申请,而是直接把之前缓存里面的内核数据结构拿出来初始化即可! slap分配器:Linux 内核 | 内存管理——slab 分配器 - 知乎 (zhihu.com) 伙伴系统:一篇看懂!伙伴系统之伙伴系统概述--Linux内存管理 - 知乎 (zhihu.com) ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/weixin_51142926/article/details/142334013
-
我看版本好低,就跑了一个 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
上滑加载中
推荐直播
-
探秘仓颉编程语言:华为开发者空间的创新利器
2025/02/22 周六 15:00-16:30
华为云讲师团
本期直播将与您一起探秘颉编程语言上线华为开发者空间后,显著提升开发效率,在智能化开发支持、全场景跨平台适配能力、工具链与生态完备性、语言简洁与高性能特性等方面展现出的独特优势。直播看点: 1.java转仓颉的小工具 2.仓颉动画三方库lottie 3.开发者空间介绍及如何在空间用仓颉编程语言开发
回顾中 -
大模型Prompt工程深度实践
2025/02/24 周一 16:00-17:30
盖伦 华为云学堂技术讲师
如何让大模型精准理解开发需求并生成可靠输出?本期直播聚焦大模型Prompt工程核心技术:理解大模型推理基础原理,关键采样参数定义,提示词撰写关键策略及Prompt工程技巧分享。
去报名 -
华为云 x DeepSeek:AI驱动云上应用创新
2025/02/26 周三 16:00-18:00
华为云 AI专家大咖团
在 AI 技术飞速发展之际,DeepSeek 备受关注。它凭借哪些技术与理念脱颖而出?华为云与 DeepSeek 合作,将如何重塑产品与应用模式,助力企业数字化转型?在华为开发者空间,怎样高效部署 DeepSeek,搭建专属服务器?基于华为云平台,又该如何挖掘 DeepSeek 潜力,实现智能化升级?本期直播围绕DeepSeek在云上的应用案例,与DTSE布道师们一起探讨如何利用AI 驱动云上应用创新。
去报名
热门标签