全部博文(175)
分类: LINUX
2013-04-12 14:52:07
Linux进程控制
进程四要素
1.有一段程序供其执行。这段程序不一定是某个进程所专有,可以与其他进程共用
2.有进程专用的内核空间堆栈
3.在内核中有一个task_struct数据结构,即通常所说的“进程控制块”(PCB)。有了这个数据结构,进程才能成为内核调度的一个基本单位接受内核的调度。
4.有独立的用户空间
进程描述
在linux中,线程、进程都使用struct task_struct来表示,它包含了大量描述进程/线程的信息:
——pid_t pid:进程号,最大值10亿。每个进程有唯一的进程号。内核线程、用户线程都对应task_struct,所以他们也有pid
——volatile long state进程状态
1.TASK_RUNNING:进程正在被CPU执行,或者已经准备就绪,随时可以执行。当一个进程刚被创建时,就处于TASK_RUNNING状态(对应就绪和执行态)2.TASK_INTERRUPTIBLE:处于等待中的进程,待等待条件为真时被唤醒,也可以被信号或者中断唤醒
3.TASK_UNINTERRUPTIBLE:处于等待中的进程,待资源有效时唤醒,但不可以由其他进程通过信号(signal)或中断唤醒
4.TASK_STOPPED:进程中止执行。当接收到SIGSTOP和SIGSTP等信号时,进程进入该状态,接收到SIGCONT信号后,进程重新回到TASK_RUNNING
5.TASK_KILLABLE:Linux2.6.25新引入的进程睡眠状态,原理类似于TASK_UNINTERRUPTIBLE,但是可以被致命信号(SIGKILL)唤醒。
6.TASK_TRACED:正处于被调试状态的进程
7.TASK_DEAD:进程退出时(调用do_exit),state字段被设置为该状态
——int exit_state进程退出时的状态
1.EXIT_ZOMBIE(僵死进程):表示进程的执行被终止,但是父进程还没有发布waitpid()系统调用来收集有关死亡的进程的信息
EXIT_DEAD(僵死撤销状态):表示进程的最终状态。父进程已经使用wait4()或waitpid()系统调用来收集了信息,因此进程将由系统删除
——struct mm_struct *mm:进程用户空间描述指针,内核线程该指针为空
——unsigned int policy:该进程的调度策略
——int prio:优先级,相当于linux2.4中goodness()的计算结果,在0--(MAX_PRIO - 1)之间取值(MAX_PRIO定义为140),其中0--(MAX_RT_PRIO - 1)(MAX_RT_PRIO定义为100)属于实时进程范围,MAX_RT_PRIO - MX_PRIO - 1属于非实时进程
数值越大,表示进程优先级越小
——int static_prio:静态优先级,与linux2.4的nice意义相同。Nice值任沿用linux的传统,在-20到19之间变动,数值越大,进程的优先级越小。Nice是用户可维护的,但仅影响非实时进程的优先级。进程初始时间片的大小仅决定于进程的静态优先级,这一点不论是实时进程还是非实时进程都一样。不过实时进程的static_prio不参与优先级计算。Nice与static_prio的关系如下:
Static_prio = MAX_RT_PRIO + nice +20
内核定义了两个宏用来完成这一转换:PRIO_TO_NICE()、NICE_TO_PRIO
——struct sched_rt_entity rt
Rt->time_slice
:时间片,进程的缺省时间片与进程的静态优先级(在linux2.4中是nice值)相关,使用如下公式得出:
MIN_TIMESLICE +((MAX_TIMESLICE - MIN_TIMESLICE) * (MAX_PRIO -1 -(p)->static_prio)/(MAX_USER_PRIO - 1))
每当创建一个进程或者线程时,都会分配如上图
在linux中用全局变量current指针指向当前正在运行的进程(线程)的task_struct
Linux进程调度
调度
从就绪的进程中选出最适合的一个来执行
学习调度需要掌握的知识点:调度策略;调度时机;调度步骤
调度策略
——SCHED_NORMAL(SCHED_OTHER):普通的分时进程
——SCHED_FIFO:先入先出的实时进程
——SCHED_RR:时间片轮转的实时进程
——SCHED_BATCH:批处理进程
——SCHED_IDLE:只在系统空闲时才能够被调度执行的进程
调度策略是某一个进程的调度策略,而非整个系统所有的进程都用某一种策略!
调度类
调度类的引入增强了内核调度程序的可扩展性,这些类(调度程序模块)封装了调度策略,并将调度策略模块化
——CFS调度类(在kernel/sched_fair.c中实现)用于以下调度策略:SCHED_NORMAL、SCHED_BATCH和SCHED_IDLE
——实时调度类(在kernel/sched_rt.c中实现)用于SCHED_RR和SCHED_FIFO策略(看pick_next_task内核函数可以知道,该类的优先级比CFS调度类高)
调度时机
调度什么时候发生?即:schedule()函数什么时候被调用
调度的发生有两种方式:
1.主动式:在内核中直接调用schedule()。当进程需要等待资源而暂时停止运行时,会把状态置于挂起(睡眠),并主动请求调度,让出CPU
例:current->state = TASK_INTERRUPTIBLE;
Schedule();//调度不会选上述进程,因为schedule只会选就绪态的进程
2.被动式(抢占):用户抢占(linux2.4、linux2.6)、内核抢占(linux2.6)
用户抢占:用户抢占发生在——从系统调用返回用户空间
——从中断处理程序返回用户空间
内核即将返回到用户空间的时候,如果need_resched标志被设置,会导致schedule()被调用,此时就会发生用户抢占
内核抢占:——在不支持内核抢占的系统中,进程/线程一旦运行于内核空间,就可以一直执行,直到它主动放弃或时间片耗尽为止。这样一些非常紧急的进程或线程将长时间得不到运行
——在支持内核抢占的系统中,更高优先级的进程/线程可以抢占正在内核空间运行的低优先级进程/线程
在支持内核抢占的系统中,某些特例下是不允许内核抢占的:
——内核正进行中断处理。进程调度函数schedule()会对此作出判断,如果是在中断中调用,会打印出错信息。
——内核正在进行中断上下文的Bottom Half(中断的底半部)处理。硬件中断返回前会执行软中断。此时任然处于中断上下文中
——进程正持有spinlock自旋锁、writelock/readlock读写锁等,当持有这些锁时,不应该被抢占,否则由于抢占将导致其他CPU长期不能获得锁而死等
——内核正在执行调度程序Schedule。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序
为保证linux内核在以上情况下不会被抢占,抢占式内核使用了一个变量preempt_count,称为内核抢占计数。这一变量被设置在进程的thread_info结构中。每当内核要进入以上几种状态时,变量preempt_count就加1,指示内核不允许抢占。每当内核从以上几种状态退出时,变量preempt_count就减1,同时进行可抢占的判断与调度
内核抢占可能发生在:
——中断处理程序完成,返回内核空间之前
——当内核代码再一次具有可抢占性的时候,如解锁及使能软中断等
调度标志
TIF_NEED_RESCHED
作用:内核提供了一个need_resched标志来标明是否需要重新执行一次调度
设置:当某个进程耗尽它的时间片时,会设置这个标志;
当一个优先级更高的进程进入可执行状态的时候,也会设置这个标志
调度步骤
Schedule函数工作流程如下:
1).清理当前运行中的进程
2)选择下一个要运行的进程;(根据调度策略来选择。pick_next_task内核函数分析:调用处于TASK_RUNNING状态的进程)
3)设置新进程的运行环境
进程上下文切换
Linux系统调用
定义
一般情况下,用户进程是不能访问内核的。它既不能访问内核中的数据,也不能调用内核中的函数。但系统调用是一个例外。Linux内核中设置了一组用于实现各种系统功能的函数,称为系统调用。用户可以通过系统调用命令在自己的应用程序中调用它们
区别
系统调用和普通的函数调用非常相似,区别仅仅在于,系统调用由操作系统内核实现,运行于内核态;而普通的函数调用由函数库或用户自己提供,运行于用户态。
库函数
Linux系统提供了一些C语言函数库,这些库函数对系统调用进行了一些包装和扩展(fread、fwrite是库函数,对系统调用做了一些封装)
系统调用数
在linux2.6.29版内核中,共有系统调用360个,可在arch/arm/include/asm/unistd.h中找到它们
工作原理概述
void main()
{
create(“testfile”,0666)<0)
}
应用程序首先用适当的值填充寄存器,然后调用一个特殊的指令让PC指针跳转到内核某一固定的位置,内核根据应用程序所填充的固定值来找到相应的函数执行。
——适当的值
在文件include/asm/unistd.h中为每个系统调用规定了唯一的编号,这个号称为系统调用号。
#define__NR_restart_syscall (__NR_SYSCALL_BASE+0)
#define__NR_exit (__NR_SYSCALL_BASE+1)
#define__NR_fork (__NR_SYSCALL_BASE+ 2)
#define__NR_read (__NR_SYSCALL_BASE+ 3)
——特殊的指令
——在intelCPU中,这个指令由中断0x80实现
——在ARM中,这个指令是SWI(已经重命名为SVC指令)
——固定的位置
在ARM体系中,应用程序跳转到的固定内核位置是ENTRY(vector_swi)。 (在
——相应的函数
内核根据应用程序传递来的系统调用号,从系统调用表sys_call_table找到相应的内核函数。
CALL(sys_restart_syscall)
CALL(sys_exit)
CALL(sys_fork_wrapper)
例:create系统调用实现原理
实现系统调用
向内核中添加新的系统调用,需要执行3个步骤:
1.添加新的内核函数
2.更新头文件unistd.h
3.针对这个新函数更新系统调用表calls.S
例:
1.在kernel/sys.c中添加函数
asmlinkage int sys_add(int a, int b)
{
return a+b;
}
/*asmlinkage:使用栈传递参数*/
2.在arch/arm/include/asm/unistd.h中添加如下代码:
#define __NR_add (__NR_SYSCALL_BASE+ 361)
3.在arch/arm/kernel/calls.S中添加代码,指向新实现的系统调用函数:CALL(sys_add)
(在calls.S中新添加的顺序必须和在unistd.h中添加的顺序相一致)
4.重新编译内核 make uImage ARCH=arm CROSS_COMPILE=arm-linux-
5.写应用程序检验:
#include
#include
main()
{
int result;
result = syscall(361, 1, 2); /*第一个参数是系统调用的编号,后两个数是给内核函数sys_add传入的参数*/
printf(“result = ”, result);
}
Proc文件系统
定义
什么是proc文件系统?
实例:通过/proc/meminfo,查询当前内存使用情况。
结论:proc文件系统是一种在用户态检验内核状态的机制
特点
——每个文件都规定了严格权限
——可以用文本编译程序读取(more、cat命令、vi程序等)
——不仅可以有文件,还可以有子目录
——可以自己编写程序添加一个/proc目录下的文件
——文件的内容都是动态创建的,并不存在于磁盘上
内核描述
内核当中一个proc文件是如何来描述的呢?
struct proc_dir_entry{
………………
read_proc_t *read_proc; //后面讲述
write_proc_t *write_proc;
………………
}
创建文件
struct proc_dir_entry * create_proc_entry(const char* name, mode_t mode,struct proc_dir_entry * parent)
功能:创建proc文件
参数:——name:要创建的文件名
——mode:要创建的文件的属性默认0755
——parent:这个文件的父目录
创建目录
struct proc_dir_entry * proc_mkdir(const char * name, structproc_dir_entry * parent)
功能:创建proc目录
参数: ——name:要创建的目录名
——parent:这个目录的父目录
删除目录/文件
void remove_proc_entry(const char *name , struct proc_dir_entry *parent)
功能:删除proc目录或文件
参数: ——name:要删除的文件或目录名
——parent:所在的父目录
读写
为了能让用户读写添加的proc文件,需要挂接上读写回调函数:read_proc、write_proc
读操作
int read_func(char * buffer, char **stat, off_t off, int count, int*peof, void *data)
参数: ——buffer:把要返回给用户的信息写在buffer里,最大不超过PAGE_SIZE
——stat:一般不使用
——off:偏移量
——count:用户读取的字节数
——peof:读到文件尾时,需要把*peof置1
——data:一般不使用
写操作
int write_func(struct file *file, const char *buffer, unsigned longcount, void *data)
参数: ——file:该proc文件对应的file结构,一般忽略
——buffer:待写的数据所在的位置
——count:待写数据的大小
——data:一般不使用
实现流程
实现一个proc文件的流程:
(1)调用create_proc_entry创建一个struct proc_dir_entry
(2)对创建的struct proc_dir_entry进行赋值:read_proc,mode,owner,size,write_proc等等
例:proc.c
proc1.c
内核异常分析
定义
Oops可以看成是内核级的Segmentation
Fault。应用程序如果进行了非法内存访问或执行了非法指令,会得到Segfault信号,一般的行为是coredump,应用程序也可以自己截获Segfault信号,自行处理。如果内核自己犯了这样的错误,则会打出Oops信息
(内核中空指针、非法指针都会产生异常。非法指针,访问地址c0000000(3G)以下的地址都会产生异常!)
分析步骤
1.错误原因提示
2.调用栈(对照反汇编代码)
3.寄存器