Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1293713
  • 博文数量: 175
  • 博客积分: 2743
  • 博客等级: 少校
  • 技术积分: 4024
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-30 01:41
文章分类

全部博文(175)

文章存档

2015年(1)

2013年(53)

2012年(71)

2011年(50)

分类: 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:进程中止执行。当接收到SIGSTOPSIGSTP等信号时,进程进入该状态,接收到SIGCONT信号后,进程重新回到TASK_RUNNING

5.TASK_KILLABLELinux2.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.4goodness()的计算结果,在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.4nice意义相同。Nice值任沿用linux的传统,在-2019之间变动,数值越大,进程的优先级越小。Nice是用户可维护的,但仅影响非实时进程的优先级。进程初始时间片的大小仅决定于进程的静态优先级,这一点不论是实时进程还是非实时进程都一样。不过实时进程的static_prio不参与优先级计算。Nicestatic_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_NORMALSCHED_BATCHSCHED_IDLE

——实时调度类(在kernel/sched_rt.c中实现)用于SCHED_RRSCHED_FIFO策略(看pick_next_task内核函数可以知道,该类的优先级比CFS调度类高

调度时机

调度什么时候发生?即:schedule()函数什么时候被调用

调度的发生有两种方式:

1.主动式:在内核中直接调用schedule()。当进程需要等待资源而暂时停止运行时,会把状态置于挂起(睡眠),并主动请求调度,让出CPU

例:current->state = TASK_INTERRUPTIBLE;

Schedule();//调度不会选上述进程,因为schedule只会选就绪态的进程

2.被动式(抢占):用户抢占(linux2.4linux2.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文件系统是一种在用户态检验内核状态的机制


特点

——每个文件都规定了严格权限

——可以用文本编译程序读取(morecat命令、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_procwrite_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:读到文件尾时,需要把*peof1

          ——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_procmodeownersizewrite_proc等等

proc.c

  1. #include   
  2. #include   
  3. #include   
  4.   
  5. #define procfs_name "proctest"  
  6. struct proc_dir_entry * Our_Proc_File;  
  7.   
  8. int procfile_read(char *buffer,   
  9.              char **buffer_location,   
  10.              off_t offset, int buffer_length, int *eof, void *data)    
  11. {  
  12.     int ret;  
  13.   
  14.     ret = sprintf(buffer, “Helloworld!\n”);  
  15.     return ret;  
  16. }  
  17.   
  18. int proc_init()  
  19. {  
  20.     Our_Proc_File = create_proc_entry(procfs_name, 0644, NULL);  
  21.     if (Our_Proc_File == NULL){  
  22.         remove_proc_entry(procfs_name, NULL);  
  23.         printk(KERN_ALERT “Error : Could not initialize /proc/%s \n”, procfs_name)  
  24.         return –ENOMEM;  
  25.       }  
  26.     Our_Proc_File->read_proc = procfile_read;  
  27.     Our_Proc_File->owner = THIS_MODULE;  
  28.     Our_Proc_File->mode = S_IFREG | S_IRUGO;  
  29.     Our_Proc_File->uid = 0;  
  30.     Our_Proc_File->gid = 0;  
  31.     Our_Proc_File->size = 37;  
  32.     printk(“/proc/%s created \n”, procfs_name);  
  33. return 0;  
  34. }  
  35.   
  36. void proc_exit()  
  37. {  
  38.     remove_proc_entry(procfs_name, NULL);  
  39.     printk(KERN_INFO “/proc/%s removed \n”, procfs_name);  
  40. }  
  41.   
  42. module_init(proc_init);  
  43. module_exit(proc_exit) ;  

proc1.c

  1. #include   
  2. #include   
  3. #include   
  4.   
  5. static struct proc_dir_entry * mydir;  
  6. static struct proc_dir_entry *pfile;  
  7.   
  8. static char msg[255];  
  9.   
  10. static int myproc_read(char *page, char **start, off_t off, int count ,int *eof, void * data)  
  11. {  
  12.     int len = strlen(msg);  
  13.       
  14.     if (off >= len)  
  15.         return 0;  
  16.     if(count > len- off)  
  17.         count = len – off;  
  18.     memcpy(page + off, msg + off, count);  
  19.     return off + count;  
  20. }   
  21. static int myproc_write(struct file * file, const char __user *buffer,  
  22.                     unsigned long count, void *data)  
  23. {  
  24. unsigned long count2 = count;  
  25.   
  26. if (count2 >= sizeof(msg))  
  27.     count2 = sizeof(msg) -1;  
  28.   
  29. if (copy_from_user(msg, buffer, count2))  
  30.     return –EFAULT;  
  31. msg[count2] = ‘\0’;  
  32. return count;  
  33. }  
  34.   
  35. static int __init myproc_init(void)     /*__init:表示该代码段位于初始化代码段(初始化代码段:该段里的函数只运行一次就会被回收,免得占用内存)*/  
  36. {  
  37.     mkdir = proc_mkdir(“mkdir”, NULL);  
  38.     if (!mydir){  
  39.         printk(KERN_ERR “Can’t create /proc/mydir\n”);  
  40.         return -1;  
  41. }  
  42.   
  43. pfile = create_proc_entry(“poll”, 0666, mydir);  
  44. if (!pfile){  
  45.     printk(KERN_ERR “Can’t create /proc/mydir/pool \n”);  
  46.     remove_proc_entry(“mydir”, NULL);  
  47.     rerurn -1;  
  48. }  
  49. pfile->read_proc = myproc_read;  
  50. pfile->write_proc = myproc_write;  
  51.   
  52. return 0;  
  53. }  
  54.   
  55. static void __exit myproc_exit(void)    /*__exit:*/  
  56. {  
  57.     remoce_proc_entry(“pool”, mydir);  
  58.     remove_proc_entry(“mydir”, NULL);  
  59. }  
  60.   
  61. module_init(myproc_init);  
  62. module_exit(myproc_exit);  
  63.   


内核异常分析

定义

Oops可以看成是内核级的Segmentation

Fault。应用程序如果进行了非法内存访问或执行了非法指令,会得到Segfault信号,一般的行为是coredump,应用程序也可以自己截获Segfault信号,自行处理。如果内核自己犯了这样的错误,则会打出Oops信息

(内核中空指针、非法指针都会产生异常。非法指针,访问地址c0000000(3G)以下的地址都会产生异常!)

分析步骤

1.错误原因提示

2.调用栈(对照反汇编代码)

3.寄存器


阅读(1455) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~