后面也会持续更新,学到新东西会在其中补充。
建议按顺序食用,欢迎批评或者交流!
缺什么东西欢迎评论!我都会及时修改的!
在这里真的很感谢这位老师的教学视频让迷茫的我找到了很好的学习视频
王晓春老师的个人空间-王晓春老师个人主页-哔哩哔哩视频
最主要的功能就是进程和内存的管理,操作系统就是让应用程序和硬件之间加了中间层。应用程序是运行在操作系统之上的,操作系统来负责协调电脑上运行多个应用程序。多个进程负责管理这些进程的资源分配。
在计算机上如果打开多个应用程序而且都在同时运行如果只有一颗CPU怎么做到的呢?答案是分成时间片。把一颗CPU的运行时间给切成特别小的时间片
也就是时间片轮转。
当然我们没有执行完毕的任务需要记录,记录到内存里面。等待下一次时间片到达再执行。这个就称之为任务之间的切换,也就是上下文切换。
需要处理程序的时候会把程序相关的信息,包括数据放到CPU的空间中。CPU中实际上也有相关的存储空间CPU里面有一级缓存二级缓存三级缓存,而且CPU缓存速度要比内存更高更快。当我们CPU在不同的任务之间切换的时候,那么在CPU缓冲区中存放的是前一个任务的数据,接下来要做另一件事那么缓冲区里面的数据可能就失效了。
比如正在刷微信缓冲区里面放的微信,这个时候打游戏CPU处理的是游戏的数据,缓冲区里面放的还是微信的数据,缓存里的数据当然没有游戏的数据,只能从内存中将游戏的数据给读取到缓冲区。利于下次访问中间必然产生一个一级缓存二级缓存里面旧的微信信息就被覆盖掉了,因空间有限过了一会又想玩微信,缓冲区里面放的是游戏。没有之前的微信信息就又被迫从内存中把微信的信息读到缓冲区里面。当然涉及到了缓冲区的内容失效这个叫做未命中。内存相比CPU慢。如果数据从缓冲区里面读出来速度就可以大幅提升,这种就叫做命中率。
人感觉没有那么敏感感觉每个任务同时执行,实际上是10ms切换。也就是一个CPU同时运行多个任务。切换是有代价的,切换要保旧的程序状态保存现场,一会回来接着执行。进程之间这种时间片的切换方式,虽然表面上给人类感觉好像舒服了同时在执行,但是对于程序来说执行效率是比较低下的,上下文切换会存在巨大的代价。
比如边学边玩手机,效率就很低。
尽可能让进程不断运行,不要这种太频繁的切换。
也可以给一个进程专门配一个对应的CPU,缓存只存当前进程的当然就快了。
进程
进程的概念
程序和进程两个很相似但不同
进程说白了是一个正在运行中的动态的一个程序,而程序比方在电脑上安装了一个软件,并不代表程序已经运行了。
一个程序可以加载到内存中可以加载多次,那么意味着每次加载的时候,它就会在内存中分配一块独立的内存空间给这个程序使用。那么程序在内存中分配空间,每一个进程都会有独立的内存空间。那个这个内存空间里的内容包括这个程序本身还有其他的比方数据等等,还有一些程序自身的一些原数据。
比方一个记事本可以打开两次在系统中对应程序只有一个,但是在内存中消耗的是两个内存空间。实际上磁盘的文件只有一个
磁盘上占了2.31MB
内存中占了47.3MB
怎么会分这么多,除了把exe文件程序加载到内存要消耗一部分空间,程序运行过程中还需要分配其他的一些内存空间比方说数据也要占空间。
一个进程里面除了程序本身还有其他一些相关内容,比方说一个进程在系统中为了和其他程序相区分,进程里面有一个唯一不同的PID,操作系统会自动分配一个唯一的编号。
还有进程所属的用户或组是什么
用户运行程序不一定进程的用户就是该用户:
用wang用户设置密码
发现是以root用户运行的
明明是wang用户执行为什么显示的是root身份运行也就是suid特殊权限导致的。
特殊权限的程序在运行的时候,不管你是谁的身份执行这个程序,将以程序所有者的身份来运行。
进程存在生命周期,程序运行一段时间,比方说程序要关闭了,进程的生命期也就终止了。
进程的创建
系统中有一个进程叫做init进程 centos7以后叫做systemd
这个进程启动以后就像人类的祖先一样,所有后续的进程都是由父进程创建的。
创建机制会采用COW(copy on write)机制
一般情况下进程创建由父进程调用fork这样一个函数然后来创建的。
进程运行的时候有父进程的
子进程就比如父进程生了小孩,还没独立父进程子进程共用一个内存空间。
18岁以后就应该字节区打拼了对吧,当子进程需要独立的时候靠COW机制当有子进程数据更新的时候,系统就会拷贝父进程的信息生成一个子进程,给它分配一个独立的内存空间。将来有什么数据变化都是放在独立的内存空间。
有写数据了数据更新了,不就是变心了。
进程、线程和协程
进程可以想象成一个公司,进程就相当于一家公司或者一个工厂,工厂生产口罩。人就相当于线程公司相当于公司。
进程里面必须有一个线程,但是公司需要找很多人,因此就变成了多线程。
当然工厂里面不可能只有员工还有原材料啥的
比如数据、代码、打开的地址空间、打开的文件,进程是包含线程的。
进程是操作系统来分配一个使用资源的单位,是资源的集合。
协程是比线程更小的一个单位了。协程和程序开发有关不同程序里面概念不一样,而且有的程序开发者里面没有协程的概念。协程可以理解成一个小函数。
进程和线程都是操作系统分配的,什么时候运行进程都是操作系统操心的,不是程序开发者负责分配的。协程的运行是由程序员负责的
怎么知道一个进程是多线程还是单线程的呢?
[root@RockyLinux9 ~]#pstree -p
查看进程号为722信息
fd是文件描述符,说白了程序打开的文件有哪些,每打开一个文件系统会分配一个数字来表示打开的文件的文件描述符。
面试题:
exe是一个软连接指向这个进程对应的程序文件路径
ps aux只能程序名称看不到完整路径,想知道程序对应的路径在磁盘在哪,进入对应进程PID进行查找。
进程PID目录下有一个status这样的文件描述当前进程的详细信息。
[root@RockyLinux9 722]#pwd
/proc/722
[root@RockyLinux9 722]#cat status
Name: firewalld
查看某个程序对应的进程编号
[root@RockyLinux9 722]#pidof passwd
2002[root@RockyLinux9 722]#ps aux | grep passwd
root 2002 0.0 0.3 18332 7040 pts/1 S+ 14:32 0:00 passwd
root 2092 0.0 0.1 6408 2176 pts/0 S+ 15:57 0:00 grep --color=auto passwd
[root@RockyLinux9 722]#ps aux | grep passwd | grep -v grep
root 2002 0.0 0.3 18332 7040 pts/1 S+ 14:32 0:00 passwd[root@RockyLinux9 722]#cat /proc/`pidof passwd`/status
进程结构
可以通过内核书学习,需要深入。
如果有多个进程是怎么管理这些进程,由一个任务列表一个数据结构来进行管理的,任务列表在内存中或有一块空间,存放了很多进程的信息。进程会有一个索引,就相当于我们有一个名单册子,这个册子里面描述了有多少的进程,进程列表实际上结构很简单就是双向列表,task_struct里面是进程信息通过指针连接起来。双向列表构成了一个任务列表,这个任务列表每一个进程在系统中都有一个叫做任务结构表,任务结构表里面描述了这个进程的相关信息。
进程描述信息叫做PCB。进程控制块里面描述了很多东西比如进程编号PID等等。
state进程状态 thread_info线程的信息,每个内容细节上又指向了新的数据结构,每一个都有独立的大索引又指向了更细的东西,比如进程的基本信息、内存结构、当前目录等等。可以理解成大数组数组里面存放很多东西,然后指针又指向别的数据。
进程相关概念
进程运行必然要分配空间,在内存中是以页(page)来分配的。大小是4k
[root@RockyLinux9 722]#getconf -a | grep -i page
PAGESIZE 4096
PAGE_SIZE 4096
_AVPHYS_PAGES 263841
_PHYS_PAGES 445918
pagesize指定了页大小,内存中分配给进程使用的内存空间至少是4k。
文件系统给文件分配磁盘空间也有一个最小单位。
页是给进程分配内存空间的最小单位,而在磁盘上给文件分配是块
程序里面运行的时候,到底分配多大的内存空间呢,那正常情况下内存里面存放了多个进程的数据,每一个进程都会在内存中分配一定的内存空间,但是对于每一个应用程序来说,每一个应用程序看不到操作系统分配那个空间从哪到哪,假设内存空间这是4G的内存
程序员运行程序的时候并不关心内存空间的物理位置。
每一个进程看到的空间叫做虚拟内存空间,并不关注物理地址空间。
创建一个数组只是起了一个名称,而这个名称代表着就是虚拟的一块内存空间。
比如int[] a a[1] a[2]就是虚拟的
虚拟地址空间里面的数据,最终还是要真正存放到物理内存里面。存的时候操作系统负责,虚拟内存最终还是要映射到物理内存中的,映射到物理内存还是由操作系统负责转化成物理内存的这样一个物理位置,转换过程需要用到硬件比如MMU,MMU叫内存管理单元负责把虚拟内存空间转为物理内存空间。
当我们程序访问内存空间的内存的时候,CPU不会把内存直接把地址发送到内存总线,而是发送到MMU,MMU把这个内存地址映射到物理内存地址上,然后通过总线再去访问内存。
MMU是一个硬件部件是放到CPU里面的,MMU属于CPU里面的一个小的组成。
当运用程序想运行的时候需要把程序访问内存空间通过MMU转换成物理内存。
为了加速访问,有一个概念叫做TLB,翻译成后备被缓冲区将虚拟地址和物理地址关系缓存起来。
用户和内核空间
以32位的操作系统为例
最多支持的内存是2的32次方也就是4G
其中的0~3G是给应用程序分配的叫做用户空间叫做虚拟内存
3G到4G存放的是内核空间
每一个应用进程误认为自己拥有了所有的虚拟内存空间,认为所有的空间都是它的。实际上就分配了一小点。
误以为拥有了全世界实际上世界不知道有多大。
进程可以分配一块空间,这块空间是由一定结构的。进程的结构大体上分成几块
第一块叫做代码块说白了程序正在运行的时候二进制程序是不是有一段可执行程序。比如.exe的文件在运行的时候需要加载到内存中放到代码块里面
数据段就是存放数据的
数据段是用来存放已经被初始化的全局变量。
[root@RockyLinux9 722]#name=wang
那么这就是一个被赋值过的一个变量这个就放到数据段里面,被初始化的数据就放到数据段里面。
BSS叫做块以符号开始的块,表示程序中未初始化的全局变量
[root@RockyLinux9 722]#declare -i n 申明一个整数
未赋值放到BSS这个块里面。
堆存放数组和对象,堆放的东西比较大
栈用来存放程序临时创建的局部变量,比如shell 脚本中的local 有效范围是函数内部
[root@RockyLinux9 722]#local n=wang
-bash: local: can only be used in a function
堆是随时可以取出来,栈是后进先出。
low memory低位内存 high memory高位内存
stack 栈数据从上到下 heap 堆从下到上
uninitialized data 未初始化数据 initialized data初始化数据
图片很详细了
内存泄漏 Memory Leak
程序申请一块内存空间,申请空间叫做malloc或者new一块内存,分配完了没有释放就会一直占用内存空间这个时候导致内存泄漏。
内存溢出 Memory Overflow
指程序申请10M的空间,但是在这个空间写入10M以上字节的数据,就是溢出。
11M数据只有10M的空间超过1M的空间,可能就放到不是程序应放到的空间,这样就会导致对别的应用程序造成了破坏
内存不足OOM
内存用完了应用程序不能运行了,所以就需要对这个内存不足情况加以干预。就是把进程中的一个占用内存相对比较大的进程给他kill。保证当前系统中内存还有一部分空间可用
OOM(out of memory)
程序占的内存空间太大,java程序是比较耗内存的。至少分配两个G内存。
原因:
给应用分配内存太少:比方说虚拟机里面可用指定使用内存的
应用用的太多,并且用完没释放,可用内存越来越少,最终就导致OOM。
设置内核参数(不推荐),不允许内存申请过量:
当内存不足的时候,就会自动找一个进程来杀死。有可能就会kill掉工作中的进程。比如数据库因为内存不够把数据库给kill了。
[root@RockyLinux9 ~]#echo 2 > /proc/sys/vm/overcommit_memory[root@RockyLinux9 ~]#echo 80 > /proc/sys/vm/overcommit_ratio [root@RockyLinux9 ~]#echo 2 > /proc/sys/vm/panic_on_oom
Linux默认是允许memory_overcommit 内存有16G结果申请32G,直接给了!申请多大内存就给多少,申请了这么多不一定马上要使用,如果用多了就设计了一个OOM killer机制 找一个进程kill掉,保证进程能指定内存空间。如果通过vm.panic_on_oom设置当OOM内存不足的时候自动重启系统,导致进程被误kill掉。
0的时候默认杀掉进程但是有可能杀掉重要的进程(默认)
2只要出现oom(也就是内存不足)立即触发重启 系统重启以后把内存清空了从头来,但是内存不足可能是因为某个程序设计的不合理,导致内存不足。
0就是申请多少就分配多少,甚至一次性超过系统总内存。内核利用某种算法猜测不合理才会拒绝
1就是申请多少就分配多少,甚至一次性超过系统总内存。画饼
2禁止申请那么多,不画饼但是也有问题,公司有风险了实实在在告诉员工也不合理。
kernel设有一个阈值,申请的内存总数超过这个阈值就算overcommit,在/proc/meminfo中可用看到这个阈值的大小
[root@RockyLinux9 ~]#grep -i commit /proc/meminfo
CommitLimit: 5086136 kB
Committed_AS: 633908 kB
CommitLimit就是一个overcommit的阈值,申请的内存超过阈值就算overcommit。
vm.overcommit_ratio 内核参数,缺省值是50,表示物理内存的50%
Committed_AS表示所有进程已经申请的内存总大小 如果Committed_AS超过了CommitLimit就表示overcommit,超出越多表示overcommit越严重。
[root@RockyLinux9 ~]#cat /proc/sys/vm/overcommit_memory
0
[root@RockyLinux9 ~]#cat /proc/sys/vm/overcommit_ratio
50
[root@RockyLinux9 ~]#cat /proc/sys/vm/panic_on_oom
0
最彻底的解决方案还是加内存,设置值不是长久的办法。
进程状态
等待IO就是网络或者磁盘
进程的更多状态:
yum -y install psmisc
- 运行态:running 就是执行
- 就绪态:ready 就绪就是等待
- 睡眠态:分为两种,可中断:interruptable,不可中断:uninterruptable
- 停止态:stoopped,暂停于内存会占空间,但不会被调度,除非手动启动。敲什么都不响应。
- 僵死态:zombie,僵尸态,结束进程,父进程结束前,子进程不关闭。对CPU不消耗对内存不消耗,重启计算机会把僵尸态进程从进程列表中移除。
进程状态
I 代表空闲内核线程
S+代表睡眠态且是前台进程 s 会话(子进程发起者)
< 高优先级进程 l 多线程进程
前台指的是占用终端 后台指的是不占用终端
第一个窗口:
[root@RockyLinux9 ~]#echo $BASHPID
2559
bash是默认开启一个子进程
PPID 父进程为之前的那个shell
[root@RockyLinux9 ~]#bash
[root@RockyLinux9 ~]#echo $BASHPID
2628
[root@RockyLinux9 ~]#echo $PPID
2559
[root@RockyLinux9 ~]#echo $PPID
2559
第二个窗口:
让父进程停止态
[root@RockyLinux9 ~]#kill -19 2559
子睡眠父停止态
-15表示正常结束进程 要销毁这个进程所占用的所有资源,回收内存资源,申请的文件描述符fd
[root@RockyLinux9 ~]#kill -15 2628
僵尸进程这一步没有变化待续
僵尸进程的申请内存和实际占用内存都是0
VSZ是申请的内存 RSS是实际内存使用情况
清理僵尸进程
父进程需要回收僵尸进程(子进程)但是此时处于睡眠
-18 恢复状态父进程状态
此时僵尸进程就不见了
两种解决方案
- kill -9 父进程(不推荐)
- kill -18 恢复父进程从停止态到睡眠态
kill不kill僵尸进程其实影响不大
大部分用户进程都应该处于睡眠态
LRU算法
LRU(least recently used)近期最少使用算法,释放内存。
哪个数据使用的最少,就将其淘汰。短期之内数据使用较多,那数据就不要淘汰了。
有限的内存空间或者缓冲区中,只放相对使用比较频繁的数据。把使用较少的数据给淘汰掉
想象人的大脑
有很多地方都有缓存CPU有缓存内存也有一块,甚至网卡里面有缓存。
IPC进程间通信
两个进程在同一个主机
pipe 管道 实现进程间的通讯
一个竖线前面的进程输出可以作为管道符后面命令的标准输入。
管道文件(fifo 先进先出)同一时间只能一个进程往里写数据另一个读
创建管道文件:
[root@RockyLinux9 data]#mkfifo /data/test.fifo
[root@RockyLinux9 data]#ll !*
ll /data/test.fifo
prw-r--r--. 1 root root 0 Dec 16 10:05 /data/test.fifo
实现管道通信:
第一个窗口
[root@RockyLinux9 data]#cat > /data/test.fifo
12345
第二个窗口
文件大小为0
[root@RockyLinux9 ~]#ll /data/test.fifo
prw-r--r--. 1 root root 0 Dec 16 10:05 /data/test.fifo
但是cat能查看到
[root@RockyLinux9 ~]#cat /data/test.fifo
12345
第一个cat往里面写数据第二个cat往外读数据
还需要注意的是管道是1对1的多一个终端都不行!
普通文件不能实现这种方式因为cat直接执行完了
双向传输socket文件
跨网络通讯可以把要给远程发的数据报文,利用TCP协议的封装,不断加报文头部来实现数据的通讯。比方在应用层添加应用层的头到达传输层添加传输层的头到达网络添加网络层的头数据链路层添加数据链路层的头,网络变成0101高低电平或者光信号就传到对方电脑上然后拆包获得数据。但是两个进程恰恰都在同一台电脑上,根本不需要跨网络通讯。
如果两个应用程序恰恰都在同一台电脑上,如果涉及到跨网络的通讯,就可以利用所谓的套接字文件来实现。
寻找套接字文件
[root@RockyLinux9 ~]#find / -type s -ls1235 0 srw------- 1 root root 0 Dec 16 11:25 /run/docker/libnetwork/7650c6db4522.sock
文件映射将文件中一段数据映射到内存,多个进程共享这片内存 也就是共享内存!
被所有进程或者部分进程共享的内存空间
信号signal
[root@RockyLinux9 ~]#trap -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
进程间通过发信号的方式,按照指定的信号来进行操作。实现了进程间通信的一种逻辑。
Lock锁
锁为了不让别人访问,如有资源已被某进程锁住,则其他进程想修改甚至读取这些资源,都将被阻塞,直到锁被打开。这种也是进程间通信的一种逻辑。
semaphore信号量 计数器
比如吃饭排队拿号
一个进程崩溃了不会影响到别的进程的使用
同一个进程不同线程之间如果一个线程出了问题会对进程产生影响
两个进程在不同主机
socket
必然要跨网络通讯TCP/IP协议,跨网络通讯得找到目标应用程序所在的主机。
socket = IP 和 端口号 通过IP找到主机,主机上可能跑了多个应用程序,每一个应用程序跨网络通讯都得分配一个与众不同的唯一的端口号,TCP协议它的端口号是0~6535 UDP也有0~6535
[root@RockyLinux9 ~]#man socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)IPV4 走TCP协议
sock.bind(HOST,PORT) IP和端口绑住
RPC远程过程调用
MQ 消息队列,生产者和消费者,如:kafka,RabbitMQ
两个进程之间跨网络相通讯
进程优先级
在Linux中每个进程运行,优先级越高资源优先获取。
优先级在系统中分配了有0~139个优先级总共140个
0~99 实时进程(给内核中的一些操作系统) 100~139 非实时进程(给用户的一些进程来使用的)
0优先级最高 139优先级最低。
有时候99对应的是0优先级
nice命令可以调用户级优先级 -20 到 19之间
-20 对应 100 19 对应的是 139 0 对应的是 120 优先级
top 指令 RT 实时优先级的缩写
p1 p2 p3 都是 ready 状态 优先级一样 代表着执行顺序一样。
那此时优先级不一样 应该运行优先级更高的进程。但是如果进程越多优先级不同,我们每次都要检测,进程越多比较效率越来越低。怎么让优先级更快的比较出来呢?
Linux准备了140个队列就相当于有140个优先级的队列
时间片到了涉及到了进程的一个调度,比140个队列看看哪个优先级最高的。
100优先级最高就运行P1
假如时间片不够执行不完,执行不完没关系,再放入到另外一个100的队列中,这个队列中放的是P1没执行完的进程称为就绪队列
按照这个逻辑100里面的进程都被执行完了。
运行队列就变成了过期队列,就绪队列变成了运行队列。
接下来比较这三个队列 还是从优先级最高的100开始比较等待100运行队列进程执行完以后再接着比101和102的运行队列。
注意这里不一定非要执行完高优先级的进程执行完,才能执行低优先级的进程。
只能做到高优先级的进程先响应。
一共有140个运行队列,140个过期队列。
比较的时候只比较就绪队列
随着进程的数量增长比较效率不会受到影响,只有140个队列。
时间复杂度就是O(1) 时间不受数据量增加影响
O(N) 线性增长
进程分类
抢占式任务:CPU的控制器不是由程序说了算,是由操作系统来负责分配,CPU到底是运行第一个进程还是运行第二个进程,由操纵系统说的算。
守护进程:说白了就算开机的时候自动运行,运行和终端没有关系。
有些程序是依附于终端的而有些程序和终端无关这个进程叫做daemon进程
平时通过浏览器上网更多是消耗网络的IO IO型密集型 大部分是交互性
压缩解密加密 消耗CPU 叫做CPU密集型 大部分都是非交互式的
查看IO调度算法
[root@RockyLinux9 ~]#cat /sys/block/sda/queue/scheduler
none [mq-deadline] kyber bfq
参考文献
1进程相关技术原理_哔哩哔哩_bilibili
2进程的各种状态详解_哔哩哔哩_bilibili
3进程和内存管理相关原理_哔哩哔哩_bilibili
总结
所有命令都需要反复敲来实验来记忆,本人基本上是个人理解加参考其他大佬的肯定有很多问题欢迎指正,我会及时修改。