标签:null running style 代码量 结构体 原理 算法介绍 区别 sys
1.进程与线程
1.0 进程:
进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
1.1 线程:
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
1.2 进程与线程的关系和区别:
进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元。
同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进程至少包括一个线程。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程是轻两级的进程,它的创建和销毁所需要的时间比进程小很多,所有操作系统中的执行功能都是创建线程去完成的。
每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程有自己的私有属性TCB,线程id,寄存器、硬件上下文,而进程也有自己的私有属性进程控制块PCB,这些私有属性是不被共享的,用来标示一个进程或一个线程的标志。
2.Linux简介
2.0 定义:
Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的UNIX工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。
2.1 特性:
多用户、多任务:Linux支持多用户,各个用户对于自己的文件设备有自己特殊的权利,保证了各用户之间互不影响。多任务则是现在电脑最主要的一个特点,Linux可以使多个程序同时并独立地运行。
良好的界面:Linux同时具有字符界面和图形界面。在字符界面用户可以通过键盘输入相应的指令来进行操作。它同时也提供了类似Windows图形界面的X-Window系统,用户可以使用鼠标对其进行操作。在X-Window环境中就和在Windows中相似,可以说是一个Linux版的Windows。
支持多种平台:Linux可以运行在多种硬件平台上,如具有x86、680x0、SPARC、Alpha等处理器的平台。此外Linux还是一种嵌入式操作系统,可以运行在掌上电脑、机顶盒或游戏机上。2001年1月份发布的Linux 2.4版内核已经能够完全支持Intel 64位芯片架构。同时Linux也支持多处理器技术。多个处理器同时工作,使系统性能大大提高。
文件系统:包含纯文本文件(ASCII)、二进制文件(binary)、数据格式的文件(data)。
3.Linux系统下进程的组织
3.0 标识符:
域名 |
含义 |
Pid |
进程标识符 |
Uid、gid |
用户标识符、组标识符 |
Euid、egid |
有效用户标识符、有效组标识符 |
Suid、sgid |
备份用户标识符、备份组标识符 |
Fsuid、fsgid |
文件系统用户标识符、文件系统组标识符 |
3.1 进程查看:
可以使用ps命令。它能显示当前运行中进程的相关信息,包括进程的PID。
ps -aux ##可以看到所有运行的程序与grep连用筛选
ps -a ##显示现行终端机下的所有程序(包括其他用户的程序)
ps -u ##以用户为主的排序显示(username)
ps -x ##显示所有程序(包括所有终端机下的)
3.2 进程创建:
fork() :fork创建一个进程时,子进程只是完全复制父进程的资源,复制出来的子进程有自己的task_struct结构和pid,但却复制父进程其它所有的资源。
#include <sys/types.h> #include <unistd.h> pid_t fork(void);
正确返回:在父进程中返回子进程的进程号,在子进程中返回0 ;错误返回:-1 。
vfolk():vfork系统调用不同于fork,用vfork创建的子进程与父进程共享地址空间,也就是说子进程完全运行在父进程的地址空间上,如果这时子进程修改了某个变量,这将影响到父进程。
#include <sys/types.h> #include <unistd.h> pid_t vfork(void);
正确返回:在父进程中返回子进程的进程号,在子进程中返回0 ;错误返回:-1 。
clone():fork()是全部复制,vfork()是共享内存,而clone() 是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的 clone_flags来决定。另外,clone()返回的是子进程的pid。
#include <sched.h> int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
正确返回:返回所创建进程的PID,函数中的flags标志用于设置创建子进程时的相关选项 ; 错误返回:-1 。
3.3 进程终止:
exit():
#include <stdio.h> #include <stdlib.h> int main() { printf("Using exit\n"); printf("This is the content in buffer"); exit(0); } //运行结果: Using exit This is the content in buffer
_exit():
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { printf("Using exit\n"); printf("This is the content in buffer"); _exit(0); } //运行结果: Using exit
exit和_exit
函数都是用来终止进程的。当程序执行到exit和_exit
时,进程会无条件的停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本程序的运行。exit可输出缓冲区数据,_exit不可输出缓冲区数据。
abort():
#include <stdio.h> #include <stdlib.h> int main() { FILE *stream; if((stream = fopen("nofilehere", "r")) == NULL) { perror("Can not open"); abort(); } else { fclose(stream); } return 0; }
abort()是使异常程序终止,同时发送SIGABRT信号给调用进程。
3.4 进程管理命令
ps: ‘ps‘是Linux 中最基础的浏览系统中的进程的命令。能列出系统中运行的进程,包括进程号、命令、CPU使用量、内存使用量等。
pstree: linux中,每一个进程都是由其父进程创建的。此命令以可视化方式显示进程,通过显示进程的树状图来展示进程间关系。
top: ‘top’是一个更加有用的命令,可以监视系统中不同的进程所使用的资源。它提供实时的系统状态信息。显示进程的数据包括 PID、进程属主、优先级、%CPU、%memory等。可以使用这些显示指示出资源使用量。
htop: htop与top很类似,但是htop是交互式的文本模式的进程查看器。它通过文字图形化地显示每一个进程的CPU和内存使用量、swap使用量。使用上下光标键选择进程,F7和F8改变优先级,F9杀死进程。Htop不是系统默认安装的,所以需要额外安装。
nice: 通过nice命令的帮助,用户可以设置和改变进程的优先级。提高一个进程的优先级,内核会分配更多CPU时间片给这个进程。默认情况下,进程以0的优先级启动。进程优先级可以通过top命令显示的NI(nice value)列查看。
renice: renice命令类似nice命令。使用这个命令可以改变正在运行的进程优先值。注意,用户只能改变属于他们自己的进程的优先值。
kill: 这个命令用于发送信号来结束进程。如果一个进程没有响应杀死命令,这也许就需要强制杀死,使用-9参数来执行。
ulimit: 该命令用于控制系统资源在shell和进程上的分配量。对于系统管理员是最有用的,可以管理重度使用和存在性能问题的系统。限制资源大小可以确保重要进程持续运行,其他进程不会占用过多资源。
w: w 提供当前登录的用户及其正在执行的进程的信息。显示信息头包含信息,如当前时间、系统运行时长、登录用户总数、过去的1,5,15分钟内的负载均衡数。
pgrep: pgrep的意思是"进程号全局正则匹配输出"。该命令扫描当前运行进程,然后按照命令匹配条件列出匹配结果到标准输出。对于通过名字检索进程号是很有用。
fg , bg:有时,命令需要很长的时间才能执行完成。对于这种情况,我们使用‘bg’命令可以将任务放在后台执行,而用‘fg’可以调到前台来使用。
ipcs: ipcs命令报告进程间通信设施状态。(共享内存,信号量和消息队列);用-p参数联合-m、-s或-q使用,可以获得相关的进程间通信的进程ID。
4.Linux下进程的状态
4.0 六种状态:
R (TASK_RUNNING),可执行状态。
S (TASK_INTERRUPTIBLE),可中断的睡眠状态。
D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。
T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态。
Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。
X (TASK_DEAD - EXIT_DEAD),退出状态,进程即将被销毁。
4.1 状态转换图:
5.Linux下进程的调度
5.0 简介:
Linux进程调度采用的是抢占式多任务处理,所以进程之间的挂起和继续运行无需彼此之间的协作。
Linux在进行进程调度的时候把进程分为两种:1.普通进程;2.实时进程;实时进程的优先级永远比普通进程的优先级高。
SCHED_OTHER 分时调度策略。
SCHED_FIFO实时调度策略,先到先服务。
SCHED_RR实时调度策略,时间片轮转。
5.2 进程优先级:
使用nice值:越大的nice值意味着更低的优先级。 (-19 ~ 20之间)
实时优先级:可配置,越高意味着进程优先级越高。
时间片:Linux中并不是以固定的时间值(如10ms)来分配时间片的,而是将处理器的使用比作为“时间片”划分给进程。
5.3 O(1)调度算法:
算法介绍:在Linux2.6版中,O(1)调度被采用,它是对非实时进程进行调度的一种调度算法。
数据结构:在O(1)调度中,要问最重要的数据结构是运行队列。运行队列描绘了进程队列的结构,在内核源码中用runqueue结构体表示。
struct runqueue { unsigned long nr_running; task_t *curr; prio_array_t *active,*expired,arrays[2]; };
#define MAX_USER_RT_PRIO 100 #define MAX_RT_PRIO MAX_USER_RT_PRIO #define MAX_PRIO (MAX_RT_PRIO + 40) typedef struct prio_array prio_array_t; struct prio_array { unsigned int nr_active; unsigned long bitmap[BITMAP_SIZE]; struct list_head queue[MAX_PRIO]; };
nice值和静态优先级之间的关系是:静态优先级=100+nice+20 而nice值的范围是-20~19,所以普通进程的静态优先级的范围是100~139
进程运行的时间片长度的计算:
静态优先级<120,基本时间片=max((140-静态优先级)*20, MIN_TIMESLICE) 静态优先级>=120,基本时间片=max((140-静态优先级)*5, MIN_TIMESLICE)
其中MIN_TIMESLICE为系统规定的最小时间片.从该计算公式可以看出,静态优先级越高(值越低),进程得到的时间片越长。 动态优先级的计算:
动态优先级=max(100 , min(静态优先级 – bonus + 5 , 139))
从上面看出,动态优先级的生成是以静态优先级为基础,再加上相应的惩罚或奖励(bonus).这个bonus并不是随机的产生,而是根据进程过去的平均睡眠时间做相应的惩罚或奖励.交互性强的进程会得到调度程序的奖励(bonus为正),而那些一直霸占CPU的进程会得到相应的惩罚(bonus为负)。
调度算法:Linux2.4版本的内核调度算法理解起来简单:在每次进程切换时,内核依次扫描就绪队列上的每一个进程,计算每个进程的优先级,再选择出优先级最高的进程来运行;尽管这个算法理解简单,但是它花费在选择优先级最高进程上的时间却不容忽视。系统中可运行的进程越多,花费的时间就越大,时间复杂度为O(n)。伪代码如下:
for (系统中的每个进程) { 重新计算时间片; 重新计算优先级; }
而2.6内核所采用的O(1)算法则很好的解决了这个问题,该算法可以在恒定的时间内为每个进程重新分配好时间片,而且在恒定的时间内可以选取一个最高优先级的进程,重要的是这两个过程都与系统中可运行的进程数无关,这也正是该算法取名为O(1)的缘故。
struct prop_array *array = rq->active; if (array->nr_active != 0) { rq->active = rq->expired; rq->expired = array; }
通过分散计算时间片、交换过期和活跃两个进程集合的方法可以使得O(1)算法在恒定的时间内为每个进程重新计算好时间片。
算法简介:从Linux2.6.23开始,考虑到O(1)调度的一些不足以及公平性方面的缺陷,所以改用完全公平调度(CFS)算法。
static const int prio_to_weight[40] = { /* -20 */ 88761, 71755, 56483, 46273, 36291, /* -15 */ 29154, 23254, 18705, 14949, 11916, /* -10 */ 9548, 7620, 6100, 4904, 3906, /* -5 */ 3121, 2501, 1991, 1586, 1277, /* 0 */ 1024, 820, 655, 526, 423, /* 5 */ 335, 272, 215, 172, 137, /* 10 */ 110, 87, 70, 56, 45, /* 15 */ 36, 29, 23, 18, 15, };548, 7620, 6100, 4904, 3906,
可以看到, nice值越小, 进程的权重越大。
CFS调度器的一个调度周期值是固定的, 由sysctl_sched_latency变量保存.
一个进程在一个调度周期中的运行时间为:
分配给进程的运行时间 = 调度周期 * 进程权重 / 所有进程权重之和
可以看到, 进程的权重越大, 分到的运行时间越多。
vruntime = 进程在一个调度周期内的实际运行时间 * NICE_0_LOAD / 进程权重 = (调度周期 * 进程权重 / 所有进程总权重) * NICE_0_LOAD / 进程权重 = 调度周期 * NICE_0_LOAD / 所有进程总权重
NICE_0_LOAD = 1024, 表示nice值为0的进程权重。
由上述公式可以看出正在运行的进程的虚拟运行时间随着运行时间的增长而增长,当它的虚拟运行时间不是可运行态进程中的最小值时它就会被其它可运行态进程抢占,而且能够看出虚拟运行时间与当前进程运行的时间、当前进程的权重的关系是:虚拟运行时间与当前进程运行的时间成正比,与当前进程的权重成反比。
由此可见,当一个进程的优先级越高,运行的时间越少时,这个进程的虚拟运行时间就越小,此进程就越应该被调度执行。
内核通过红黑树来对虚拟运行时间进行排序,红黑树的最左侧结点的虚拟运行时间最少,所以该结点所表示的进程将是下一个要被调度的进程。
6.浅谈Linux进程模型
进程作为操作系统重要的核心之一,其发展形成经过了上万名高级程序员之手,千万级别的代码量,可见其工程的浩大。在不断地改革创新之后,进程模型渐臻完美,然而仍存在不足,例如:程序并发执行时付出了巨大的时空开销,每个进程在进行切换时身上带了过多的“累赘”导致系统效率降低,故而引入了线程。
进程模型建立在大量的数据结构基础上,不断地翻陈出新,如由早期的Linux 2.1版本的O(n)调度器到 Linux 2.6版本的O(1)调度器再到CFS调度器。
进程模型作为人类的高智慧集中产物,基于人类科学,在硬件的基础上实现软件层面的诸多功能,其深奥的原理仍有待我们进一步学习探究。
7.参考链接
标签:null running style 代码量 结构体 原理 算法介绍 区别 sys
原文地址:https://www.cnblogs.com/hooyee5215/p/8907146.html