欢迎各位兄弟 发布技术文章
这里的技术是共享的
如下图
Application就是进程的运行位置
如下图,
进程是通过双向链表?????来管理的,List(链表),这个链表是有次序的,通过一个可以找到下一个,进程之间?????在内核内部靠一个独立的数据结构Task_Structure 来管理的,这是C语言来描述的一种独立数据组织的数据结构,这个文件整体结合起来也被称为进程描述符
每一个进程都有进程描述符 (process descriptor),存的是进程的元数据,类似于文件的元数据
每一个进程的描述符彼此之间有关联性的,在内核内部通过一个双向链表,(不必太深究)来进行组织,
在创建一个进程的时候,首先要创建进程描述符,并将其添加到此链表上,
删除一个进程,就要删除进程描述符,,然后内核不能再追踪这个进程了
创建一个进程,除了给进程分配CPU,内存等,还要在内核的内存空间中维护一个进程描述符文件,里面保存的是当前进程的所有相关信息
如下图
是进程描述符的结构,还有许许多多的子结构,
进程描述符从上往下:进程的状态state:ready,stop,stopped,sleeping (interrupt sleeping,uninterrupt sleeping),running(也可以理解为ready),Zombie(僵尸),进程内部可包括多个线程,于是thread_info,usage flags(用法标识),run_list tasks (运行的列表),mm(memory map 内存映射的信息),real_parent parent(父进程),,tty(tty所关联的终端),,,,thread,fs(file system) file,signal pending(正在传送和接收的信号?正在处理的信号?)
mm_struct 有描述符
thread 有描述符?????
tty_struct 有描述符
fs_struct 有描述符
files_struct 有描述符 就是inode,通常称为描述符了,
signal_struct 有描述符
多任务就是一个cpu上运行多个进程,所以进程切换不可避免,
如下图
进程切换即上下文切换(Context switch),
假设进程A切换成进程B,A的进程描述符被挂起,被Suspend,就要保存现场(保存在cpu内运行的stack pointer,other registers,EIP register, etc,,,,,,,保存在内存中的内核的进程描述符当中了) (进程描述符文件大小是固定的,),,,B进程resume,恢复现场了,把B的进程描述符文件读进来,(stack pointer读进CPU,other registers,EIP register读进寄存器)(EIP就是指令计数器吧)(每一个进程的other registers各不相同),EIP就指向下一条指令了,于是就装载并执行下一条指令,,,,,,这就是上下文切换
上下文切换是需要时间的,上下文切换由内核完成,不是进程自己完成,,,每一个进程切换由用户模式转到内核模式,再到用户模式,(假如用到系统调用,就又到内核模式...............),一直这样切换.,,,进程切换必须由内核来完成.
cpu时间有两部分:用户空间的所有时间加起来,内核空间的所有时间加起来
所有内核的时间(#top命令中的%sy)
%us 就是用户模式
%sy 就是系统模式,内核模式,,,,,,,内核模式不应该占据太多时间,因为工作是靠用户模式(比如web进程),如果大量时间在内核上,很可能是进程切换,中断次等过多导致的
如下图
上下文切换太频繁不好(内核占据过多时间),不频繁也不好(比如鼠标,键盘,要等好久,才会起作用)
linux: 支持进程抢占的
优先级高的进程可以抢占优先级低的进程,不是一上来随时可以抢的,而是有内部系统时钟,内部的时间嘀嗒,tick(嘀嗒),一秒中嘀嗒几次,就是时间解析度?????解析度越高,时钟频度越精准,,,内核内部有时间频率,100Hz(一秒钟嘀嗒100次,一个嘀嗒10毫秒),1000Hz(一秒钟嘀嗒1000次,一个嘀嗒1毫秒),操作系统根据嘀嗒的次数来决定时间走动的
时钟中断:每一次嘀嗒产生一个可抢的时钟中断
A:运行完要5ms, 时钟嘀嗒是1ms,,那么C在一次嘀嗒后抢过来后,,A剩下的4ms下次运行,,,若时钟嘀嗒是10ms,则A运行完了(只需要5ms),C才在一次嘀嗒后抢过来
C:
进程类别:
交互式进程(I/O): I/O密集型,比如编辑器,大量的时间等在I/O上,对cpu量少,,
批处理进程(CPU):CPU密集型,耗cpu大量的时间,比如守护进程,nginx服务器,,比如科学计算的进程,,比如处理mysql的查询
实时进程(Real-time): 随时要运行,必须要立即得到响应的,优先级非常高的进程
PC桌面:交互式进程,通常应该高优先级
服务器: 它是CPU密集性的,不一定吧,有可能服务器也大量从硬盘读文件吧
一般调度器这样处理:CPU密集性的,时间片长,但优先级低;; I/O密集型时间片短,但优先级高
linux内优先级(priority)分为三类:
实时优先级: 实时进程的优先级吗?????通常与内核相关的,或者非常关键性的任务相关的, 1-99,数字越小,优先级越低
静态优先级: 通常用户空间的进程的优先级 100-139,数字越小,优先级越高
总范围: 1-139 (实际0-139),可以理解成1-139
所有的实时优先级比静态优先级的优先程度更高
如下图 # top 命令
PR:priority优先级,,,,,,RT:real time,表示是实时优先级,(1-99之间的某一个值),20表示100之后的值,即120?
NI: nice值,
http://honglus.blogspot.com/2011/03/understanding-cpu-scheduling-priority.html
$ ps -e -o class,cmd | grep sshd #查看优先级
$ ps -e -o class,rtprio,pri,nice,cmd #查看实时优先级
[root@localhost ~]# ps -e -o class,rtprio,pri,nice,cmd # -e所有跟终端相关的和无关的查程
-o表示自定义显示的字段 class:调度类别 ,,,,,,,,,rtprio,(real time priority)实时优先级,,,,,,,,,,pri,优先级,静态优先级,,,,,,,,,,nice,调整静态优先级的
CLA: class 类别,调度类别 FF是FIFO TS就是Others
RTPRIO: real time priority 实时优先级
NI: nice值,调整静态优先级的 -20,19 对应于静态优先级的100,139,,所以nice值为0,静态优先级就是120,,,默认启动每一个进程的nice值就是0(即静态优先级是120)
/sbin/init 是用户空间的进程,不是实时进程 (所谓的init进程,它是第一个由内核启动的用户级进程。)??????,NI值为0,所以优先级是120(这里怎么是19??)(因为进行了动态优先级的调整??????)
[kthreadd]这里加上中括号,表示是一个内核线程?????,,,有些内核线程的优先级是实时的,比如[migration/0],[watchdog/0],它们是PRI是没有意义的,RTPRIO是99,意味着实时优先级里面最高的,意味着只要它想运行,就一定得运行,,,,其它的如[ksoftirqd/1]是正常的优先级,PRI是19,是以用户优先级的方式来定义的
内核在实现调度的时候,对两种不同优先级的进程进行调度的方法是不一样的
内核的调度类别
三个调度器分别用来调度不同优先级的进程,不同类的进程
实时进程:调度器有两个
SCHED_FIFO: scheduler first in first out,调度先进先出,先运行完了,其它进程才能运行,调度方法很粗糙
SCHED_RR: scheduler Round Bobin 调度是轮调的,每个进程有时间片,就算是实时的?????,优先级一样,时间片运行完了,就换同级别的下一个,
SCHED_OTHER: (linux中)scheduler other 调度其它的,调度用户空间(100-139之间的)进程的,,,, 在unix (SCHED_NORMAL) 未必调度的都是用户空间的线程,用来调度100-139之间的优先级的进程,,按优先级进行调度
除了有些实时进程之外,大多数进程,包括内核线程都是用户空间运行(都以other来调度)
假设好多 100-139
10个110
30个115
50个120
2个130
根据优先级,130的进程始终运行不上去,
内核中引入了动态优先级的概念
动态优先级
主要是对100-139的进程,内核随时监控着这些进程,某一进程很长时间没有运行了,内核在自己的内部会临时性的调高它的优先级,,,,比如假设 把 130调成 105,立刻优先级变高, 由此它就运行了,临时调整,非永久调整,,,,,,,有时,因某进程过多频繁的运行,也会调低优先级,,,
dynamic priority = max (100, min ( static priority - bonus + 5, 139))
# static priority - bonus 就是静态优先级 - 奖惩措施 bonus的范围是 0-10之间
假如 110, 3
它的优先级为 max(100,min(110-3+5,139) 结果为112
假设因动态优先级的调整专门向外提供服务的进程的优先级太低,,,因为本来提供服务的进程的优先级太高,对提供服务的进程进行了惩罚,,,但是本来规划提供服务的进程的优先级应该高点,所以要手动调整优先级
手动调整优先级
100-139: 调整nice值
nice N COMMAND: 表示以这个值来启动命令
renice -n # PID: 已经启动的进程修改
chrt -p [prio] PID #也能调整100-139 #chrt 即 change real time
1-99:
chrt: ( change real time priority 改变实时优先级 )
chrt -f -p [prio] PID #fifo类别的
chrt -r -p [prio] PID #rr类别的
chrt -f -p [prio] COMMAND #启动命令时直接指定优先级
ps -e -o class,rtprio,pri,nice,cmd #查看实时优先级
[root@localhost ~]# man chrt
Cannot open the message catalog "man" for locale "zh_CN.UTF-8"
(NLSPATH="/usr/share/locale/%l/LC_MESSAGES/%N")
Formatting page, please wait...
CHRT(1) Linux User’s Manual CHRT(1)
NAME
chrt - manipulate real-time attributes of a process
SYNOPSIS
chrt [options] prio command [arg]... #启动时直接指定优先级命令
chrt [options] -p [prio] pid #-p优先级 pid是进程id号
DESCRIPTION
chrt(1) sets or retrieves the real-time scheduling attributes of an
existing PID or runs COMMAND with the given attributes. Both policy
(one of SCHED_OTHER, SCHED_FIFO, SCHED_RR, SCHED_BATCH, or SCHED_IDLE)
and priority can be set and retrieved.
The SCHED_BATCH policy is supported since Linux 2.6.16. The SCHED_IDLE
policy is supported since Linux 2.6.23.
OPTIONS
-p, --pid
operate on an existing PID and do not launch a new task
-b, --batch #调整批处理进程类别????
set scheduling policy to SCHED_BATCH (Linux specific)
-f, --fifo #调整fifo类别
set scheduling policy to SCHED_FIFO
-i, --idle #调整空闲进程类别????
set schedulng policy to SCHED_IDLE (Linux specific)
-m, --max
show minimum and maximum valid priorities, then exit
-o, --other #调整other类别
set policy scheduling policy to SCHED_OTHER
-r, --rr #调整rr类别
set scheduling policy to SCHED_RR (the default)
-h, --help
display usage information and exit
-v, --version
output version information and exit
如下图,有数百个进程,
linux2.6以后的版本,将所有进程分为139*2的队列,优先级为1的排在1号队列上,
优先级为2的排在2号队列上,,,,,,优先级为99的是优先级最高的,扫描139(140)个队列的首部(就是每一个队列的第一个进程吧),从99到1,再从100到139,从每个队列里取一个??? 按从高到低排,,,这里把2号第一个拿出来运行,再2号第二个拿出来运行,再2号第三个拿出来运行,,,然后把1号第一个拿出来运行,,,最后把139号.........
如下图
假设139号第一个未运行完,只运行了一次10s,还需要5s才能运行完,,,,每个列队有两个,一个叫活动队列,一个叫过期队列,从活动队列运行完后(未运行完成)就放到过期队列里,,,下图就是把三个放到过期队列里,,,,当第二次运行的时候,就把活动队列和过期队列调换下,(省得复制和重新排队了)
如下图,,每一次扫描,139(140)个,,挑出优先级最高的那个,,,这就是O(1)算法,,,,,,,好像linux2.6.18之后的内核里,运行的是CFQ的队列( Complete Fair Queue 完全公平队列 有人称为CFS,Complete Fair Scheduler 完全公平调度器 ),主要是用来实现SCHED_OTHER的,,,,,,CFQ不太适合于服务器,适合于桌面系统,
如下图
进程的格式,一个进程的内部,在它的线性地址空间里面,从0x0000,到0xffff,有一段空间是不能使用的,线性地址空间是4G(32位系统上),但有1G是留给内核,,,,进程自己的空间当中,从低地址开始,放的是只读段(代码指令区 Text),,Data区(有了初始数据的变量),,BSS(初始化为0的变量),,堆(比如打开的文件内容,通常在堆上),,栈(本地变量,函数的参数,函数的返回值,返回地址????等等,,,, 程序执行过程中的变量所保存的值,随时可能被撤销),,,,,,栈是从高地址空间向低地址空间增长的,堆是从低地址空间向高地址空间增长的,
如下图
所谓的init进程,它是第一个由内核启动的用户级进程。
linux如何创建进程,COW机制( Copy On Write 在写的时候才复制 ),linux每一个进程由父进程生成,第一个进程是init,由Kernel生成的,,,其它所有进程由init(或其子进程)来生成,,,除了init进程,其它进程都有父进程,每一个进程由父进程fork()系统调用生成,,,,创建一个进程,最重要的是创建进程描述符task_struct(这是由内核来完成),,,,一个进程要有内存空间,pid(进程id),ppid(父进程id)
一个进程刚创建时,内存的地址指向与父进程是同一个位置,
memory-->parent
当父进程或子进程需要写的时候,它们才分家
COW:Copy On Write
这样才会大大降低进程的资源开销,因为很多子进程只是执行额外的操作,而不是写额外的数据,就退出了,,,所以很多子进程不需要创建额外的地址空间的,这大大降低了进程创建的开销,
但是创建进程反正是有开销的,apache的prefork模型,任何一个用户请求来了,要用进程响应,当请求批量退出以后,进程要销毁,再来一批,再创建,,,,,然后销毁,,,再创建,所以性能不高,