什么是进程
磁盘上有很多文件,但它只有在运行的时候才会成为进程。进程是运行中的程序的一个副本,是被载入内存的一个指令集合,是资源分配的单位。
- 进程ID(Process ID,PID)号码被用来标记各个进程
- UID、GID、和SELinux语境决定对文件系统的存取和访问权限
- 通常从执行进程的用户来继承
- 存在生命周期
进程的创建
- init:第一个进程,从 CentOS7 以后为systemd
- 进程:都由其父进程创建,用fork()函数创建子进程。机制叫做 COW:Copy On Write(写时复制)
什么是写时复制?
父进程P1都有自己占用的一块内存空间,它有一个子进程P2。如果P2只是被P1创建出来,里面的数据没有发生更新,那么P1和P2其实指向的是同一块内存空间。直到P2发生了数据的更新,它才会复制一份新的内存空间,用来存放自己的数据。
进程,线程和协程
进程是整个系统中用来分配资源的一个单位,进程里面存放了很多的资源,里面有数据、程序代码、打开的文件、地址空间。由于进程需要处理这些数据,它需要找一个帮他干活的人,这个人就是线程了,一个进程里面至少会有一个线程。所以如果把进程看成一个公司,线程就是公司里面的员工。进程包含线程,线程隶属于进程,多个线程共享某些资源,这就是进程和线程之间的逻辑关系。
协程Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性。简单来说可以理解为它是程序员写的一段代码。
#列出当前系统中的所有进程和线程
pstree -p
#查看进程中的线程数
grep -i threads /proc/进程的PID编号/status
进程结构
linux中运行着多个进程,内核把进程存放在叫做任务队列(task list) 的双向循环链表中。链表中的每一项都是类型为task_struct,称为进程控制块(Processing Control Block),PCB中包含一个具体进程的所有信息。
进程控制块PCB包含信息:
- 进程id、用户id和组id
- 程序计数器
- 进程的状态(有就绪、运行、阻塞)
- 进程切换时需要保存和恢复的CPU寄存器的值
- 描述虚拟地址空间的信息
- 描述控制终端的信息
- 当前工作目录
- 文件描述符表,包含很多指向file结构体的指针
- 进程可以使用的资源上限(ulimit –a命令可以查看)
- 输入输出状态:配置进程使用I/O设备
进程相关概念
Page Frame: 页框,用存储页面数据,存储Page 4k。
我们在使用进程的时候,必须要给他分配内存空间,这里就会用到一个单位,页(Page),它是分配内存的最小单位,大小一般为4K。
#用linux命令查看页的大小
getconf -a |grep -i size
物理地址空间和虚拟地址空间
MMU:Memory Management Unit 负责虚拟地址转换为物理地址。
程序在访问一个内存地址指向的内存时, CPU不是直接把这个地址送到内存总线上,而是被送到MMU(Memory Management Unit),然后把这个内存地址映射到实际的物理内存地址上,然后通过总线再去访问内存,程序操作的地址称为虚拟内存地址。
TLB:Translation Lookaside Buffer 翻译后备缓冲区,用于保存虚拟地址和物理地址映射关系的缓存。
说人话就是,假设我服务器的内存有4个G,我想运行磁盘上的某个应用程序,它就会分配一块内存空间出来。如果这时候程序想去内存中处理数据了,它就必须知道这个数据在什么位置。但事实上每个程序每一次运行,操作系统给它分配的内存的物理空间地址都是不同的,这就产生了一个问题,程序不知道确定地址怎么去内存中访问数据呢?其实当一个应用程序去访问数据的时候,虽然物理地址不固定,但是 相对位置是固定的,也就是说站在程序的角度上看,它所看到的不是一个绝对的空间,而是一个虚拟的地址空间,它是一个相对的地址。操作系统会通过MMU(硬件中的一个设备,由CPU来实现)把虚拟地址转换成物理地址。由于每次转换都要做计算,所以引入了TLB缓存来提高性能。
用户和内核空间
对于每一个应用程序来讲,它并不知道自己是和其他应用程序共存的。在每个应用程序的眼里,它看到的只是虚拟内存,以为自己拥有了整个内存空间。
C代码和内存布局之间的对应关系
每个进程都包括5种不同的数据段
- 代码段:用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需 要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。
- 数据段:用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局 变量。
- BSS段:Block Started by Symbol”的缩写,意为“以符号开始的块,BSS段包含了程序中未初始化的 全局变量,在内存中 bss段全部置零。
- 堆(heap):存放数组和对象,堆是用于存放进程运行中被动态分配的内存段,它的大小并不固 定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆 上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
- 栈(stack):栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量 (但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时, 其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。 由于栈的后进先出特点,所以栈特别方便用来保存/恢复调用现场。可以把堆栈看成一个寄存、交换临时数据的内存区。
进程使用内存问题
内存泄漏:Memory Leak
指程序中用malloc或new申请了一块内存,但是没有用free或delete将内存释放,导致这块内存一直处 于占用状态。
内存溢出:Memory Overflow
指程序申请了10M的空间,但是在这个空间写入10M以上字节的数据,就是溢出。
内存不足:OOM
OOM 即 Out Of Memory,“内存用完了”情况在java程序中比较常见。
进程状态
基本状态
- 创建状态:进程在创建时需要申请一个空白PCB(process control block进程控制块),向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态。
- 就绪状态:进程已准备好,已分配到所需资源,只要分配到CPU就能够立即运行。
- 执行状态:进程处于就绪状态被调度后,进程进入执行状态。
- 阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用。
- 终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行
进程更多的状态:
- 运行态:running
- 就绪态:ready
- 睡眠态:分为两种,可中断:interruptable,不可中断:uninterruptable
- 停止态:stopped,暂停于内存,但不会被调度,除非手动启动
- 僵死态:zombie,僵尸态,结束进程,父进程结束前,子进程不关闭,杀死父进程可以关闭僵死 态 的子进程
#观察进程
ps aux
#统计进程数量
top
#停止进程
kill -19 PID
#恢复停止的进程
kill -18 PID
#杀死进程
kill -9 PID
LRU 算法
LRU:Least Recently Used 近期最少使用算法(喜新厌旧),释放内存。
内存的空间终究有限,不可能把所有的数据都放到内存中处理。所以一般只存储经常用的数据,旧的数据会被淘汰。
IPC 进程间通信
IPC: Inter Process Communication
同一主机:
- pipe 管道,单向传输
- socket 套接字文件,双工通信
- Memory-maped file 文件映射,将文件中的一段数据映射到物理内存,多个进程共享这片内存
- shm shared memory 共享内存
- signal 信号
- Lock 对资源上锁,如果资源已被某进程锁住,则其它进程想修改甚至读取这些资源,都将被阻塞,直到锁被打开
- semaphore 信号量,一种计数器
不同主机:socket=IP和端口号
- RPC remote procedure call
- MQ 消息队列,生产者和消费者,如:Kafka,RabbitMQ,ActiveMQ
范例:利用管道文件实现IPC
#在XSHELL新建2个终端
#在其中一个终端创建管道文件
mkfifo test.fifo
#往里面写一句话
echo hello > test.fifo
#在另一个终端查看文件可以读取到刚才的话
root@DMIT-iJboSKPbP4:~# cat test.fifo
hello
范例:查找socket类型的文件
find / -type s -ls
进程优先级
优先级范围 | 描述 |
---|---|
0-99 | 实时进程,实时优先级,越大优先级越高 |
100-139 | 非实时进程,系统优先级。越小优先级越高。 |
-20-19 | NICE值,对应系统优先级的100-139。 |
#查看进程编号,命令,优先级
ps axo pid,cmd,ni,pri,rtprio
#把某个进程PID的优先级改成5
renice -n 5 PID
进程分类
操作系统分类:
- 协作式多任务:早期 windows 系统使用,即一个任务得到了 CPU 时间,除非它自己放弃使用
CPU ,否则将完全霸占 CPU ,所以任务之间需要协作——使用一段时间的 CPU ,主动放弃使用。 - 抢占式多任务::Linux内核,CPU的总控制权在操作系统手中,操作系统会轮流询问每一个任务是
否需要使用 CPU ,需要使用的话就让它用,不过在一定时间后,操作系统会剥夺当前任务的 CPU
使用权,把它排在询问队列的最后,再去询问下一个任务。
进程类型:
- 守护进程:daemon,在系统引导过程中启动的进程,和终端无关进程。
- 前台进程:跟终端相关,通过终端启动的进程。 注意:两者可相互转化
按进程资源使用的分类:
- CPU-Bound:CPU 密集型,非交互
- IO-Bound:IO 密集型,交互