linux shell操作 - 05 进程 与 IO 模型

文章目录

  • 计算机内存分配
  • 进程与子进程
  • IO模型
  • 非阻塞IO
  • IO多路复用
  • 网络IO模型
    • 简单的socket
    • 并发的socket

计算机内存分配

一个32位,4G内存的计算机,内存使用分为两部分:

  • 操作系统内核空间
  • 应用程序的用户空间
  • 使用的操作系统不同,分配方式不同;
    在这里插入图片描述

进程与子进程

  • 进程是操作系统中资源管理的最小单位,它是将静态程序加载到内存中的一次动态的执行,包括进程创建、进程调度、进程销毁;

  • 每个进程有自己独有的内存(在用户空间内),进程的私有内存是相互独立的,且进程间无法直接通信;

  • 不同进程间通信,可以采用队列、管道、信号、共享内存(内核空间内)等方式;

  • 每个进程中可以创建一个或者多个线程,多个线程共享当前进程的部分内存资源,如代码、全局变量等;

  • 进程的内存分布
    在这里插入图片描述

  • 比如上图中的shell脚本,运行时,shell是父进程,python3开启子进程,父进程会等待子进程退出;当关闭shell进程(父进程)时,python3子进程由init进程接管。

  • 父进程中,当以fork()系统调用创建子进程时,子进程执行exit()系统调用退出后,内核中仍然存有子进程的信息,如pid, exit code, run time等,这些信息要等父进程通过wait/waitpid来回收,若一直未回收,则退出的子进程成为僵尸进程,一直占用系统资源。

  • 僵持进程无法通过kill关闭,随着数量增多,系统资源耗尽,导致系统瘫痪。

  • 可以进行IO(input输入、output输出)操作的内核对象
  • 如文件、管道、socket…
  • 流的入口是fd (file descriptor);

IO模型

  • 阻塞IO, 一直等待,不占用资源;无法同时处理多个任务;
    用户进程发起读的系统调用,当内核中socket fd未就绪时,一直阻塞等待;
    socket fd 就绪时,将内核中的socket数据拷贝到用户空间(拷贝期间阻塞等待);
    accept()阻塞
    在这里插入图片描述

  • 非阻塞IO, 忙轮询,占用CPU;
    应用程序不断轮询内核,对应的socket fd是否就绪,未就绪则返回(非阻塞);
    若已就绪,则拷贝内核中socket的数据到用户空间(阻塞)。
    accept()不阻塞
    在这里插入图片描述

  • IO多路复用,多个IO复用一个进程/线程,既可以阻塞等待不占用资源,又可以同时并发处理多个任务;

    • linux 支持select, poll, epoll
    • 应用程序通过系统调用让内核同时监控多个socket fd,一旦有网络事件发生,内核就遍历找到对应的socket,将其标记为可读,然后将所有的socket fd返回给应用程序;
    • 应用程序遍历所有的fd,找到就绪的fd,通过系统调用复制对应socket的数据到用户空间;
      在这里插入图片描述
  • 异步IO;

  • 应用程序发起异步read操作后,立即返回;

  • 内核中的fd就绪,复制数据完成,触发信号通知应用程序;

  • 全程无阻塞;
    在这里插入图片描述

  • 信号驱动IO

  • 首先注册信号处理函数;

  • 检查内核socket fd 是否就绪,未就绪直接返回;

  • 已就绪,则内核发送信号给应用程序,触发信号处理函数;

  • 信号处理函数,发起系统调用,从内核空间拷贝socket数据到用户空间(阻塞);
    在这里插入图片描述

 

非阻塞IO

  • 忙轮询,占用CPU;
  • 性能不如阻塞IO;
  • 代码流程,不停地 遍历所有的fd,查看是否就绪;
    在这里插入图片描述

 

IO多路复用

多个IO复用一个进程/线程,既可以阻塞等待不占用资源,又可以同时并发处理多个任务;

  • select

    • 最大连接数默认1024;
    • 两次拷贝,先将所有的fd 从用户空间拷贝到内核空间,由内核监控是否有fd就绪(可读或可写),也就是有网络事件发生;一旦有fd就绪,则遍历所有的fd集合,找到对应的fd并将其 标记为就绪态(可读、可写),然后将所有的fd(fd集合)从内核空间拷贝到用户空间,用户进程内遍历所有的fd,找出就绪的从进行读写;
    • 两次遍历
    • 并发量大时,性能指数式下降;
    • 代码流程:
      在这里插入图片描述
  • poll,与select 没有本质的区别,只是连接数比select多;

    • select 使用固定长度的 BitMap表示文件描述符集合,而poll 使用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,还会受到系统文件描述符限制。
    • select/poll 都是使用线性结构存储进程的 socket 集合,都需要遍历文件描述符集合来找到可读或可写的 socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合。
  • epoll,高性能的IO多路复用(仅linux支持)

    • 连接数更大,上限为进程的最大连接数;查看最大连接数cat /proc/sys/fs/file-max
    • 内核中采用红黑树结构,可高效地增删查(O(logn)),仅返回就绪的fd;
    • 将就绪的fd拷贝给用户进程,避免无用的遍历;
    • 适合并发量大的场景,可以解决C10K问题(单台服务器并发1w),
      在这里插入图片描述
    • 操作流程,使用epoll_create 创建内核epoll对象;epoll_ctl将要监控的socket加入红黑树,内核检测到有网络事件发生,则将对应的socket 连接放入一个就绪链表中,并复制给用户空间(epoll_wait返回);
      在这里插入图片描述
    • epoll支持水平触发边缘触发,边缘触发效率更高;select/poll仅仅支持水平触发,即内核socket缓冲区有数据就绪,在数据没有被进程读取完之前,会多次通知进程来读取;而边缘触发则仅仅通知一次,需要进程一次性读取所有的数据。例如,当快递放入快递站点时,管理员可能给你打多个电话催促你取快递,这种通知多次的方式就是水平触发;而当快递放入快递柜时,就只给你发送一次短信,仅仅通知一次,这种仅仅通知一次的方式就是边缘触发。
    • IO多路复用中有socket 就绪,并不一定可读、可写,此时为避免进程阻塞,需要结合非阻塞IO一起使用。

 

网络IO模型

  • 基于socket网络通信
    在这里插入图片描述
  • 客户端与服务端建立连接的过程
    • 客户端的socket对象调用connect((ip, port));
    • 服务器的网卡接收请求,并转发给OS ,实现TCP三次握手;同时在操作系统内核中维护两个队列,TCP半连接队列 & TCP全连接队列;半连接队列表示未完成三次握手,全连接队列表示完成三次握手,已完成socket连接;
    • 内核从TCP全连接队列取出当前socket连接,存储到内核文件列表中,同时将其fd返回用户空间,存入进程数组;
    • 应用程序中就可以拿着这个已连接的socket进行读写;
    • 读/写时 就对应阻塞IO、非阻塞IO、IO多路复用、异步IO、信号驱动的IO的情况;
      在这里插入图片描述

下面以python3语言为例演示socket的使用。

简单的socket

同时只能处理一个客户端的请求。

server:

import socket
import time
import sys
import signal  # 注册信号的处理函数def handler(signum: int, frame):"""接收到SIGINT信号时,打印一句话,并退出进程"""print("received signal:", signum)sys.exit(0)# 注册信号的处理函数
signal.signal(signal.SIGINT, handler)  # 使用 Ctrl + C 发送SIGINT信号# 网络层使用Ipv4
# 传输层使用TCP
sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 默认为阻塞的socket# 绑定ip
sock_server.bind(("localhost", 8000))
# 最多监听1000个连接
sock_server.listen(1000)  # 监听的socketwhile True:# TCP三次握手完成,接收socket连接conn, addr = sock_server.accept()  # accept阻塞 等待 socket连接就绪# 处理当前的socket连接,conn是已连接的socketprint("conn:", conn, addr)# 读 IO操作data = conn.recv(1024)  # 阻塞等待 内核中socket就绪,并拷贝数据到用户空间 print("received data:", data.decode())# 写 IO操作,拷贝到内核空间,写入socket缓冲区conn.send(b"hello, i am server. I have got your data.")# 在当前socket连接 的请求处理完之前,服务端不会接收下一个客户端的socket连接time.sleep(20)

client:

import socket
import time# 创建客户端
sock_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 建立连接
sock_client.connect(("localhost", 8000))# 发送数据
sock_client.send(b"hello, i am jack")
print("发送数据完成.")# 接收数据
data = sock_client.recv(1024) # 接收 1024 bytes    # 阻塞  内核fd未就绪->就绪  内核socket数据复制到用户空间
print("received data:", data.decode())

并发的socket

同时可以处理多个客户端的请求。

  • 阻塞+多进程
    • 随着请求数量的增多,子进程越来越多,(fork创建子进程复制父进程所有的资源)占用的系统资源越来越多,并发量大时会影响系统的性能,甚至导致系统崩溃;
    • 多进程上下文的切换包括用户空间、内核空间,消耗系统性能;
    • 所以并发量特别大时,多进程不是理想的方案。
# server.py
import socket
import os
import time
import sys
import signal  # 注册信号的处理函数
import multiprocessingdef handler(signum: int, frame):"""接收到SIGINT信号时,打印一句话,并退出进程"""print("received signal:", signum)sys.exit(0)# 注册信号的处理函数
signal.signal(signal.SIGINT, handler)  # 使用 Ctrl + C 发送SIGINT信号# 处理请求
def handle_request(conn, addr):print("subprocess:", os.getpid())print("conn:", conn, addr)flag = Falsewhile not flag:# 接收数据data = conn.recv(1024) # recv from kernelprint("received data:", data.decode())# 发送数据conn.send(b"I am server. I have got your data.")# 检测客户端的断开data = conn.recv(1024)print("客户端断开:", data.decode())if not data:conn.close()print("客户端已断开.")flag = Trueprint(f"{os.getpid()}子进程退出.")if __name__ == "__main__":# 网络层使用Ipv4# 传输层使用TCPsock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 默认为阻塞的socket# 绑定ipsock_server.bind(("localhost", 8000))# 最多监听1000个连接sock_server.listen(1000)  # 监听的socketwhile True:# TCP三次握手完成,接收socket连接conn, addr = sock_server.accept()  # accept阻塞 等待 socket连接就绪# 父进程 阻塞等待连接print("创建子进程.")# 子进程处理 请求sub_process = multiprocessing.Process(target=handle_request, args=(conn, addr))sub_process.daemon = Truesub_process.start()# client.py
import socket
import time# 创建客户端
sock_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 建立连接
sock_client.connect(("localhost", 8000))# 发送数据
sock_client.send(b"hello, i am jack")
print("发送数据完成.")# 接收数据
data = sock_client.recv(1024) # 接收 1024 bytes
print("received data:", data.decode())sock_client.close() # 客户端断开连接,会发送空数据到服务端
  • 阻塞+多线程
    • 线程是轻量级进程,同一个进程的多个线程可以共享当前进程的部分资源(代码、全局变量等),避免了过多的资源消耗;
    • 多线程的上下文切换,虽比多进程轻量,但大量的线程来回切换,也会给系统造成不小的开销;
    • 多线程需要考虑线程安全问题,另外每个线程也有自己的栈空间,也消耗内存;大量的线程必然会消耗大量的栈空间;
    • 所以对于特别大的并发量时,多线程也不是理想的方案。
在这里插入代码片
  • IO多路复用

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

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

相关文章

信奥编程 1168:大整数加法

解析:在c中需要考虑这么几个问题,第一个是大数据的输入,第二个是大数据的存储,第三是大数据的计算方式,最后是输出。 针对上述几个问题,第一个问题,采用字符串的方式或者数组加循环的方式接收输…

Linux下给普通用户添加sudo权限

背景:业务上需要自己创建Linux用户,然后给这个用户添加sudo权限 第一步:输入下面的命令 visudo第二步:找到文件中的这一行 root ALL(ALL) ALL,在这一行的下面添加以下内容 username ALL(ALL) ALL其中,us…

YOLOv8-pose关键点检测:Backbone优化 |EMO,结合 CNN 和 Transformer 的现代倒残差移动模块设计 | ICCV2023

💡💡💡本文解决什么问题:面向移动端的轻量化网络模型EMO,它能够以相对较低的参数和 FLOPs 超越了基于 CNN/Transformer 的 SOTA 模型,替换YOLOv8 backbone Yolov8-Pose关键点检测专栏介绍:https://blog.csdn.net/m0_63774211/category_12398833.html ✨✨✨手把手教…

抖音团购小程序怎么开通?怎么做抖音团购?

餐饮同行们已经纷纷上架了抖音团购服务,还没入局的商家还在等待什么呢?如果你还没有抓住这个流量的红利期,那就真的OUT了!为了在这个竞争激烈的市场中脱颖而出,建议你尽快行动起来,打造一个属于自己的抖音团…

[栈思想]后缀表达式

​ 后缀表达式 题目描述 后缀表达式是指这样的一个表达式:式中不使用括号,运算符号放在两个运算数之后,所有计算按运算符号出现的顺序,严格地自左而右进行(不用考虑运算符的优先级) 如:3*(5-…

C#实体类与XML互转以及List和DataTable转XML的使用

引言 在C#开发中,数据的存储和传输是非常常见的需求。使用XML作为数据格式有很多优点,例如可读性强、易于解析等。而实体类、List和DataTable是表示数据模型的常用方式。本文将介绍如何在C#中实现实体类、List和DataTable与XML之间的相互转换&#xff0c…

Mac_mac常用快捷键

锁屏 锁屏 controlcommandq 截图与录屏」 command shift 3:截图整个屏幕command shift 4:截图所选区域command shift 5:录制屏幕 区域可选 「浏览器快捷键」 command T:打开新标签页command W:关闭当前标签…

【数据结构—单链表的实现】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 1. 链表的概念及结构 2. 单链表的实现 2.1单链表头文件——功能函数的定义 2.2单链表源文件——功能函数的实现 2.3 单链表源文件——功能的测试 3.具体的理解操作…

二叉树OJ题讲解之一

今天我们一起来做一道初级的二叉树OJ题,都是用递归思想解答 力扣965.单值二叉树 链接https://leetcode.cn/problems/univalued-binary-tree/description/ 所谓单值二叉树就是这棵二叉树的所有节点的值是相同的,那我们要做这道题,肯定要…

适用于iOS 的顶级苹果数据恢复软件

数据丢失可能随时发生在任何人身上,这可能是一种令人沮丧的经历。丢失 iOS 设备上的重要数据可能会造成特别严重的损失,因为其中可能包括有价值的照片、联系人、消息和其他重要文件。幸运的是,有多种数据恢复工具可以帮助用户恢复丢失的数据。…

部署jenkins一直显示Please wait while Jenkins is getting ready to work

部署jenkins一直显示Please wait while Jenkins is getting ready to work … 需要你进入jenkins的工作目录 vim .jenkins/hudson.model.UpdateCenter.xml将https://updates.jenkins.io/update-center.json更换为更改为https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates…

Python基础:推导式(Comprehensions)详解

1. 推导式概念 Python推导式(comprehensions)是一种简洁而强大的语法,用于从已存在的数据(列表、元组、集合、字典等)中创建新的数据结构。推导式包括: 列表推导式元组推导式字典推导式集合推导式 2. 列表…

Vue 实现低代码开发平台,没想到这么好用!

前言 在众多开发技术中,Vue 组件化开发技术以其卓越的灵活性和高效性备受瞩目。 低代码平台相信不少人知道它的存在,而且现在大部分公司都在开发自己的低代码平台,首先我们来看看低代码平台可视化界面: 官网:https:/…

跨标签页通信的8种方式(下)

跨标签页通信是指在浏览器中的不同标签页之间进行数据传递和通信的过程。在传统的Web开发中,每个标签页都是相互独立的,无法直接共享数据。然而,有时候我们需要在不同的标签页之间进行数据共享或者实现一些协同操作,这就需要使用跨…

1233:单词倒置(C语言)

题目描述 最近birdfly收到了女友的几份信件,为了只要他俩知道信件的秘密,女友把信件里的每个单词都倒置了。这样只有birdfly将它们倒置过来才能明白女友的心思了。为此birdfly还特意请你编写程序帮他解决一下这个问题。 简单起见假定每封信只包含英文单词…

DDD全网最通俗易懂讲解(一)

基础概念 领域 领域就是用来确定范围的,范围即边界,这也是DDD在设计中不断强调边界的原因。简言之,DDD的领域就是这个边界内要解决的业务问题域。领域可以进一步划分为子领域。一个领域相当于一个问题域,领域拆分为子域的过程就…

阶段二:进阶知识(掌握Python的常用设计模式)

Python中常用的设计模式包括工厂方法模式。 工厂方法模式是一种创建型模式,它提供了一种创建对象的接口,但具体创建的对象类型可以在运行时决定。这种模式使得创建对象的行为延迟到其子类中,使得代码更加灵活、可扩展和维护性更高。 在Pyth…

GCM的Counter模式加密与解密java实现

GCM即Galois/Counter Mode,指的是加密采用Counter模式,并且带有GMAC消息认证码 import lombok.extern.slf4j.Slf4j;import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParam…

详解二叉树

【本节目标】 1.树的概念和结构 2.二叉树的概念和结构 3.二叉树的顺序结构及实现 4.二叉树的链式结构及实现 1.树的概念及结构 1.1树的概念 树是一种非线性的数据结构,它由一个根结点和n(>0)个子树构成,之所以叫做树,是因为它很像生活…

ArkTS开发webview,html页面中的input和按钮等操作均无响应 【Bug已解决-鸿蒙开发】

文章目录 项目场景:问题描述原因分析:解决方案(根据此方法即可解决此Bug):本文相关知识本Bug常规排除步骤ArkTS项目场景: 在鸿蒙开发过程遇到的问题: 问题 ArkTS API9 使用webview加载的html,页面中的按钮和input等操作均无响应 是有相关API设置webview是否可以touch或…