分类: LINUX
2008-05-10 22:13:23
1 基本概念
1.1. 进程和线程
可执行文件由指令和数据组成。进程就是在计算机上运行的可执行文件针对特定的输入数据的一个实例,同一个可执行程序文件如果操作不同的输入数据就是两个不同的进程。
线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每个线程共享其所附属的进程的所有的资源,包括打开的文件、页表(因此也就共享整个用户态地址空间)、信号标识及动态分配的内存等等。线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一物理内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。
Linux在核外采用1:1线程模型,即用一个核心进程(轻量进程)对应一个线程,把线程调度等同于进程调度,交给核心完成,而其它诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。因此可以把进程看作一组线程,这组线程拥有相同的线程组号(TGID),这个TGID就是这组线程序所附属的进程的ID号,每个线程的ID号就是我们用ps命令所看到的LWP号。
为了方便,从现在起我们用任务来代替进程和线程,即每提到任务,我们就是指线程和进程,除非要强调线程和进程之间的不同之处。任务的周期从被fork开始一直到给任务从进程表中消失。一个进程包括:正文段(text),数据段(data),栈段(STACK)和共享内存段(SHARED MEMORY)。
1.2. 中断和信号
(1)中断
中断通常定义为用来改变CPU执行的指令的顺序的事件。对于其分类可谓是仁者见仁,智者见智,但毕竟有胜于无,这里给出一种分类:硬中断、异常中断和软中断。硬中断也称为外部中断,分为两类,可屏蔽和不可屏蔽。/proc/interrupts列出了当前系统定义的所有硬中断。
异常中断是系统运行出现异常时候CPU自动产生的中断,如除数为零、使用虚拟内存机制时的缺页保护异常等。软中断是由程序指令中包含INT 指令产生的中断,如单步跟棕;该中断处理的一部分任务可以延迟一会再处理。
1.3. CPU 的状态
CPU的状态可分为有7种:
(1)常规用户态(目态): CPU所执行的任务在访问该任务自己的内存空间;
(2)核心态(管态):如果CPU正在运行核心程序或在CPU上运行的任务正在通过系统调用请求内核服务,例如访问硬件,这时就称CPU处于核心态;
(3)CPU运行nice任务,一个优先级别低于普通任务的优先级别的任务;
(4)io等待: 由于任务等待I/O而使CPU处于空闲状态,这些I/O主要指block I/O,raw I/O,VM paging/swapins;
(5)闲置:系统中的所有任务由于等待除了I/O以外的事件的发生而处于睡眠状态,或者系统没有任务;
(6)CPU正在处理硬中断,即 在irq状态;
(7)CPU正在处理软中断。
从用户态转换为核心态的唯一途径是中断。CPU处于用户态时,所运行的程序只能执行非特权指令,如果用户程序在用户态下执行特权指令,把发生中断,由操作系统获得控制。操作系统在核心态下运行。从核心态到用户态可以通过修改程序状态字来实现,这把伴随这由操作系统程序到用户程序的转换。
1.4. 任务优先级
每个任务在Kernel2.6 中不是由调度器统一计算,而是独立计算。优先级由两部分构成:
(1)静态优先级。Nice是进程的静态优先级。静态优先级在任务创建的时候就被赋值,并且不变(除非用系统调用改变任务的nice值);
(2)动态优先级(task->counter-MAX_RT_PRIO)。它定义了一个在就绪队列的进程当它得到CPU后可运行的时间。计算机是以时钟中断作为时间的计数器,每发送一个时钟中断,动态优先级上的时间片就减少一个时钟中断的时间,时间片减到0的时候就退出该进程而执行另一个进程。任务的动态优先级则是跟静态优先级和平均等待时间(sleep_avg)有关。对于实时任务的优先级在创建的时候就确定了,而且一旦确定以后就不再改变,所以下面部分仅对于非实时任务而言,任务的平均等待时间越大,任务的动态优先级也就越高。
有以下几种情况需要计算任务的优先级:
(1)创建新任务,使用函数effective_prio()(因为此时任务尚未进行调度,没有sleep_avg和interactive_credit可言);
(2)唤醒等待任务时,使用函数recalc_task_prio ()来计算任务动态优先级。
(3)任务用完时间片以后,被重新插入到active array或者expired array的时候需要
(4)其它情况,如IDLE任务初始化等时候。
动态优先级的计算公式为:PRI=NICE+40+CPU_ PENALTY,从公式中可以看到大多数用户任务的优先级是大于40的。可以使用“ps -l”和“ps -emo THREAD”命令分别查寻任务和线程的CPU使用状态。使用“nice -n proname”和“renice +n proid”来修改任务的优先级。nice值的系统缺值为20。
|
1.5. CPU队列长度
一个任务如果拥有了除CPU以外的所有运行时所需要的资源,我们就称该任务为可运行任务。可运行任务包含等待队列和正在CPU上运行的任务,这些任务构成了运行队列。运行队列长度为任务的个数,运行队列越长,任务等待时间越长。
一个阻塞任务可能在等待I/O数据或等待一个系统调用的结果。当一个任务即把进入运行队列时,内核首先计算其优先权,然后再放入相应的优先级的运行队列里;在运行过程中,可运行的任务的优先级每秒更新一次,因此其在可运行队列的位置可动态调整。
1.6. 上下文切换的比率
CPU一般在某一时刻只能运行一个任务。为了给用户一个并行的感觉,Linux内核不停在各个任务之间切换,这个切换叫做上下文切换。上下文包括:CPU的所有寄存器中的值、任务的状态以及堆栈中的内容。上下文切换的主要任务是保存老任务CPU状态,并加载新任务的保存状态,用新任务的内存映像替换老任务的内存映像。因此上下文切换导致大量信息的转移,导致了昂贵的上下文切换开销。因此,要尽可能减少该切换。
要减少切换,必须知道切换怎么样发生。在下列情况下发生上下文切换:
任务结束;
任务使用完时间片,为使各个任务能公平地使用CPU,内核通过时间中断来实现调度,不能的体系结构以及不同的内核,每秒时间中断的次数不一样;
任务需要的资源当前不可用(如缺页)或任务等待I/O操作的完成;
当睡眠任务被唤醒进入可运行队列时,如果该任务的优先级高于所有可运行的任务而且正在运行的任务可被抢占;
任务利用信号或系统调用自动放弃CPU;
1.7. 任务状态
任务总共有6种状态标志,分别是:
(1)可运行状态:可运行状态是那些正在等待CPU资源的任务的状态,这些任务在就绪队列run-queqe中.这些任务只要得到CPU在个资源就马上可以被运行
(2)可打断睡眠状态:处于等待队列中的任务,待资源有效时唤醒,也可由其它任务被信号中断、唤醒后进入就绪状态
(3)不可打断睡眠状态:处于等待队列中的任务,直接等待硬件条件,待资源有效时唤醒,不可由其它任务通过信号中断、唤醒;这类状态的任务其睡眠的时间相对比较短。与可打断睡眠状态的区别就是后者可以由信号唤醒。
(4)僵死状态:虽然此时已经释放了内存、文件等资源,但是还没有释放任务控制块task_struct数据结构项。它不进行任何调度或状态转换,等待父任务把它彻底释放
(5)暂停状态:可能是任务控制信号所致,或者正在被跟踪调试,而导致暂时停止运行;需要其它任务的信号才能唤醒。任务被暂停,通过其它任务的信号才能唤醒。正在调试的任务可以在该停止状态。
(6)TAS_DEAD:已经退出且不需要父任务回收的任务的状态。
一个任务只能运行在用户方式(CPU处于用户态)或核心方式(CPU处于核心态)下。在用户方式下任务使用一般的堆栈,而在核心方式下用的是固定大小的堆栈(一般为一个物理内存页大小)。
1.8. 系统平均负载
系统平均负载用来衡量系统工作量(服务的任务的数目)的指标。Linux系统采用指数衰减移动平均算法来计算系统平均负载,即当前系统的负载=上次计算的负载 衰减因子 + 这一时刻系统的任务数目*(1-衰减因子)。所用的任务种类包括运行队列的任务和不可打断睡眠状态的任务。缺省时,Linux系统提供1分钟,分钟和15分钟三种形式的系统平均负载,为方便,我们就称之为1-分钟平均负载、5-分钟平均负载、15-分钟平均负载。时间越短,衰减越快(衰减大),越能反映系统的负载突变情况;时间越长,衰减越慢,能反映系统的平均情况。
1.9. 进程的capability
传统UNIX的访问控制模型非常简单,就是“超级用户对普通用户”模型。在这种模型中,一个进程或帐户要么什么都能做即具有全部的系统权限,要么几乎什么也不能做即只有很小的权限,这取决于进程的UID。例如,如果一个进程需要加载/卸载内核模块以及管理文件系统等操作时,就需要完全的root权限。很显然这样做对系统安全存在很大的威胁。UNIX系统中的SUID问题就是由这种信任状模型造成的。例如,一个普通用户需要使用ping命令。这是一个SUID命令,会以root的权限运行。而实际上这个程序只是需要加载/卸载内核模块,除此之外的其它 root的权限对这个程序都是没有必要的。如果程序编写不好,就可能被攻击者利用,获得系统的控制权。
使用能力(capability)可以减小这种风险。系统管理员为了系统的安全可以剥夺root用户的某些能力,这样即使root用户也把无法进行某些操作;而这个过程又是不可逆的,也就是说如果一种能力被删除,除非重新启动系统,否则即使root用户也无法重新添加被删除的能力。
当特权操作由Capability LSM 模块控制时,系统基于进程信任状(creds)来仲裁特权操作。当Capability未被编译进内核时,内核使用默认的安全模块(security/dummy.c)仲裁特权操作,机制非常简单,仅仅检查进程euid、fsuid(进行文件系统相关特权操作时)是否为0。在这种情况下,dummy模块根本不关心进程的信任状,每个进程的信任状都拷贝其父进程的信任状。追根溯源,每个进程的信任状无论进程用户是否为超级用户,最终都拷贝Init进程的信任状,信任状中包含有超级用户进程的所有权能。
Linux是怎么样使用POSIX capabilities代替传统的信任状模型的?每个进程有三个和能力有关的位图:inheritable(I)、permitted(P)和effective(E),对应进程描述符 task_struct(include/linux/sched.h)里面的cap_effective, cap_inheritable, cap_permitted。每种能力由一位表示,1表示具有某种能力,0表示没有。
cap_effective。当一个进程要进行某个特权操作时,操作系统会检查 cap_effective的对应位是否有效,而不再是检查进程的有效UID是否为0。例如,如果一个进程要设置系统的时钟,Linux的内核就会检查 cap_effective的CAP_SYS_TIME位(第25位)是否有效,
cap_permitted表示进程能够使用的能力。在cap_permitted中可以包含cap_effective中没有的能力,这些能力是被进程自己临时放弃的,也可以说cap_effective是cap_permitted的一个子集。进程放弃没有必要的能力对于提高安全性大有助益。例如,ping只需要CAP_NET_RAW,如果它放弃除这个能力之外的其它能力,即使存在安全缺陷,也不会对系统造成太大的损害。
cap_inheritable表示能够被当前进程执行的程序继承的能力。
Linux实现了7个POSIX 1003.1e规定的能力,还有21个(截止到2.6.13版本的内核)Linux所特有的,这些能力在/usr/src/linux/include/linux/capability.h文件中定义。其细节如下:
能力 编号 解释
CAP_CHOWN 0 允许改变文件的所有权
CAP_DAC_OVERRIDE 1 忽略对文件的所有DAC访问限制
CAP_DAC_READ_SEARCH 2 忽略所有对读、搜索操作的限制
CAP_FOWNER 3 如果文件属于进程的UID,就取消对文件的限制
CAP_FSETID 4 允许设置setuid位
CAP_KILL 5 允许对不属于自己的进程发送信号
CAP_SETGID 6 允许改变组ID
CAP_SETUID 7 允许改变用户ID
CAP_SETPCAP 8 8 允许向其它进程转移能力以及删除其它进程的任意能力
CAP_LINUX_IMMUTABLE 9 允许修改文件的不可修改(IMMUTABLE)和只添加(APPEND-ONLY)属性
CAP_NET_BIND_SERVICE 10 允许绑定到小于1024的端口
CAP_NET_BROADCAST 11 允许网络广播和多播访问
CAP_NET_ADMIN 12 允许执行网络管理任务:接口、防火墙和路由等,详情请参考/usr/src/linux/include/linux/capability.h文件
CAP_NET_RAW 13 允许使用原始(raw)套接字
CAP_IPC_LOCK 14 允许锁定共享内存片段
CAP_IPC_OWNER 15 忽略IPC所有权检查
CAP_SYS_MODULE 16 插入和删除内核模块
CAP_SYS_RAWIO 17 允许对ioperm/iopl的访问
CAP_SYS_CHROOT 18 允许使用chroot()系统调用
CAP_SYS_PTRACE 19 允许跟踪任何进程
CAP_SYS_PACCT 20 允许配置进程记帐(process accounting)
CAP_SYS_ADMIN 21 允许执行系统管理任务:加载/卸载文件系统、设置磁盘配额、开/关交换设备和文件等。详情请参考/usr/src/linux/include/linux/capability.h文件。
CAP_SYS_BOOT 22 允许重新启动系统
CAP_SYS_NICE 23 允许提升优先级,设置其它进程的优先级//
CAP_SYS_RESOURCE 24 忽略资源限制
CAP_SYS_TIME 25 允许改变系统时钟
CAP_SYS_TTY_CONFIG 26 允许配置TTY设备
CAP_MKNOD 27 允许使用mknod()系统调用
CAP_LEASE 28 Allow taking of leases on files
()