Chinaunix首页 | 论坛 | 博客
  • 博客访问: 251767
  • 博文数量: 52
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1538
  • 用 户 组: 普通用户
  • 注册时间: 2013-04-24 07:45
个人简介

生活就像海洋,只有意志坚强的人,才能到达彼岸。

文章存档

2013年(52)

分类: LINUX

2013-08-18 13:55:20

一、Linux进程控制

1>定义

    进程:进程时一个执行中的程序。它是动态的实体。

    程序:程序是存放在磁盘上的一系列代码和数据的可执行映像,是一个静止的实体。

2>进程四要素

    1) 有一段程序供其执行。这段程序不一定是某个进程所专有,可以与其他进程共用。

    2)有进程专用的内核空间堆栈

    3)在内核中有一个task_struct数据结构,即通常所说的“进程控制块”。有了这个数据结构,

进程才能成为内核调度的一个基本单位接受内核的调度。

    4) 有独立的用户空间。


3>进程描述
    
    在Linux中,线程、进程都使用struct task_struct来表示,它包含了大量描述进程/线程的信

息,其中比较重要的有:

    1)pid_t pid;  进程号,最大10亿;
    
    2)volatile long state/* 进程状态*/

        a、TASK_RUNNING:进程正在被CPU执行,或者已经准备就绪,随时可以执行。当一个进程刚

被创建时,就处于TASK_RUNNING状态。

        b、TASK_INTERRUPRIBLE:处于等待中的进程,待等待条件为真时被唤醒,也可以被信号或

者中断唤醒。
        
        c、TASK_UNINTERPUTIBLE: 处于等待中的进程,带资源有效时被唤醒,但不可以由其它进程

通过信号(signal)或中断唤醒。

        d、TASK_STOPPEND:进程中止执行。当接收到SIGSTOPSIGTSTP等信号时,进程进入该状

态,接收到SIGCONT信号后,进程重新回到TASK_RUNNING

        e、TASK_KILLABLE: Linux2.6.25新引入的进程睡眠状态,原理类似于 

TASK_UNINTERPUTIBLE,但是可以被致命信号(SIGKILL)唤醒。

        f、TASK_TRACED: 正处于被调试状态的进程。
    
        g、TASK_DEAD:进程退出(调用do_exit),state字段被设置为该状态。

    3)int exit_state/*进程退出时的状态*/

        EXIT_ZOMBIE(僵死进程):表示进程的执行被终止,但是父进程还没有发布waitpid()系统调

用来收集有关死亡的进程的信息。

        EXIT_DEAD(僵死撤销状态):表示进程的最终状态。父进程已经使用wait4()或waitpid()系                            

统调用来收集了信息,因此进程将由系统删除。

    4)struct mm_struct* mm  进程用户空间描述指针,内核线程该指针为空。

    5)unsigned int policy 该进程的调度策略。

    6)int prio 优先级 (0-(MAX_PRIO-1))MAX_PRIO定义为140,0-100属于实时进程范围,

值越大,表示进程优先级越

    7)int static_prio 静态优先级。

    8)struct sched_rt_entity rt

        a、rt->time_slice:时间片,进程的缺省时间片与进程的静态优先级相关,使用一下公式得出:

    MIN_TIMESLICE + ((MAX_TIMESLICE - MIN_TIMESLICE) * (MAX_PRIO - 1 - (P)->static_prio) / (MAX_USER_PRIO - 1))

优先级数值越大,则分配的时间片越小

3>Task_struct位置




4>Current
    
    在Linux中用current指针指向当前正在运行的进程的task_struct。

5>进程创建

6>进程撤销

    进程销毁可以通过几个事件驱动--通过正常进程结束、通过信号或是通过对exit函数的调用。不

管进程如何退出,进程的节点胡都要借助对内核函数do_exit的调用。

二、Linux进程调度

1>调度:从就绪的进程中选出最适合的一个来执行。调度策略、调度时机、调度步骤。

2>调度策略

    1)SCHED_NORMAL(SCHED_OTHER):普通的分时进程

    2)SCHED_FIFO:先入先出的实时进程

    3)SCHED_RR:时间片轮转的实时进程

    4)SCHED_BATCH:批处理进程

    5)SCHED_IDEL:只在系统空闲时才能够被调度执行的进程

    6)调度类的概念:
        CFS调度类(在kernel/sched_fair.c中实现)用于以下调度策略:SCHED_NORNAL、

SCHED_BATCH和SCHED_IDEL。

        实时调度类(在kernel/sched_rt.c中实现)用于SCHED_RR核SCHED_FIFO策略。

pick_next_task:选择下一个要运行的进程

2>调度时机
    
调度什么时候发生即:schedule()函数什么时候被调用。调度的方式有两种:

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

    2)被动式(抢占
    用户抢占(2.4,2.6)
    用户抢占发生在:
    *从系统调用返回用户空间
    *从中断处理程序返回用户空间

内核即将返回用户空间的时候,如果need_resched标志被设置,会导致schedule()被调用,此时就会发生用户抢占。        

    内核抢占(2.6)
     *在不支持内很抢占的系统中,进程/线程一旦运行于内核空间,就可以一直执行,知道主动放弃或时间片耗尽为止。这样一些非常紧急的进程或线程将长时间得不到运行。
    
    *在支持内核抢占的系统中,更高优先级的进程/线程可以抢占正在内核空间运行的低优先级进程/线程。   
    
    *在支持内核抢占的系统中,某些特例下时不允许内核抢占的:
        1、内核正进行中断处理
       
     2、内核正在进行中断上下的Bottom Half(中断的底半部)处理。硬件中断返回前会执行软中断,此时任然处于中断上下文中

        3、进程正持有spinlock自旋锁、writelock/readlock读写锁等,当持有这些锁时,不应该被抢占,否则由于抢占将导致其他CPU长期不能获得锁而死等。

        4、内核正在执行调度程序Scheduler。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序。


    为保证Linux内核在以上情况下不会被抢占,抢占式内核使用了一个变量preempt_count,称为内核抢占计数。这一变量被设置在进程的thread_info结构中,每当内核要进入以上几种状态时,变量preempt_count就加1,指示内核不允许抢占。每当内核从以上几种状态退出时,变量preempt_count就减1,同时进行可抢占的判断与调度。

    内核抢占可能发生在:

    *中断处理程序完成,返回内核空间之前
    *当内核代码再一次具有可抢占性的时候,如解锁及使能软中断等

3)调度标志(TIF_NEED_RESCHED)
作用:
    内核提供了一个need_resched标志来表明是否需要重新执行一次调度。

设置:
    当某个进程耗尽它的时间片时,会设置这个标志;
    当一个优先级更高的进程进入可执行状态的时候,也会设置这个标志。

3>调度步骤

    1)清理当前运行中的进程;
   
    2)选择下一个要运行的进程;(pick_next_task

    3)设置新的进程的运行切换;

    4)进程上下文切换。



三、Linux系统调用

1>定义:一般情况下,用户空间是不能访问内核的。它既不能访问内核中的数据,也不能调用内核中

的函数。但系统调用是一个例外。Linux内核中设置了一组用于实现各种系统函数功能的函数,称为

系统调用,用户可以通过系统调用命令在自己的应用程序中调用它们。

系统调用和普通的函数调用非常相似,区别仅仅在于,系统调用由操作系统内核实现,运行于内核

态;而普通的函数调用由函数库或用户自己提供,运行于用户态。

2>库函数

    Linux系统还提供了一些C语言函数库,这些库函数对系统调用进行了一些包装和扩展。

3>系统调用数

    在2.6.36/38版内核中,共有系统调用369个。可在arch/arm/include/asm/unistd.h中找到它

们。

4>工作原理

应用程序首先用适当的值填充寄存器,然后调用一个特殊的指令跳转到内核某一固定的位置,内核根

据应用程序所填充的固定值来找到相应的函数执行

1)适当的值
    在文件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)

2)特殊的指令
在Intel CPU中,这个指令由中断0x80实现
在ARM中,这个指令时SWI(已经重命名为svc指令)。

3)固定的位置
在ARM体系中,应用程序跳转的固定内核位置是ENTRY(vector_swi)

4)相应的函数
内核根据应用程序传递来的系统调用号,从系统调用表sys_call_table找到相应的内核函数。
CALL(sys_restart_syscall)
CALL(sys_exit)
CALL(sys_fork_wrapper)


5>实现系统调用
向内核中添加新的系统调用,需要执行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 + 370)
    3在arch/arm/kernel/calls.S中添加代码,指向新实现的系统调用函数:
CALL(sys_add)
    4重新编译内核

四、Proc文件系统

1>定义

proc文件系统是一种在用户态检查内核状态的机制
通过/proc/meminfo,查询当前内存使用情况。

2>子目录/文件名
apm:高级电源管理信息

bus:总线以及总线上的设备

devices:可用的设备信息

driver:已经启用的驱动程序

interrupts:中断信息

ioports:端口使用信息

version:内核版本

3>特点

1)每个文件都规定了严格的权限
    可读?可写?哪个用户可读?哪个用户可写?

2)可以用文本编辑程序读取(more命令,cai命令,vi程序等等)

3)不仅可以文件,还可以有子目录

4)可以自己编写程序添加一个/proc目录下的文件。

5)文件的内容都是动态创建的,并不存在于磁盘上。

4>内核描述
struct proc_dir_entry{
............................................
read_proc_t* read_proc;
write_proc_t* write_proc;
...............

1)创建文件:
struct proc_dir_entry* create_proc_entry(const char* name, mode_t mode, struct proc_dir_entry*parent)

功能: 创建proc文件
参数:
    name:创建的文件名
    mode:要创建的文件的属性 默认0755
    parent:这个文件的父目录
}   

2)创建目录:
struct proc_dir_entry* proc_mkdir(const char* name, struct proc_dir_entry* parent)

功能:
创建proc目录
参数:
    name:要创建的目录名
    parent:这个目录的父目录  


为了能让用户读写添加的proc文件,需要挂接上读写回调函数:
read_proc
write_proc

3)读操作:
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:一般不使用

4)写操作
int write_func(struct file* file,const char* buffer,unsigned long count,void* data)

参数:
    file:该proc文件对应的file结构,一般忽略。
    buffer:待写的数据所在的位置
    count:待写数据的大小
    data:一般不用
5>实现一个proc文件的流程

    1)调用create_proc_entry创建一个struct proc_dir_entry.
    
    2)对创建的struct proc_dir_entry进行赋值:read_proc,mode, owner,size,write_proc等等。

    实例分析:

五、Linux内核异常 

常在河边走,哪有不湿鞋,内核级的程序,总有死机的时候,如果运气好,会看到一些所谓"Oops"信
息。

Oops可以看成是内核级的Segmentation Fault。应用程序如果进行了非法内存访问或执行了非法指

令,会得到Segfault信号,一般的行为是coredump,应用程序也可以自己截获Segfault信号,自行处

理。如果内核自己犯了这样的错误,则会打出Oops信息。

分析步骤:

1、错误原因提示

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

3、寄存器(pc)
阅读(1866) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~