linux进程管理学习笔记

linux 进程管理

1 linux进程控制

进程的四个要素:

有一段程序供其执行

有专用的内核空间椎栈

内核中有一个tash_struct数据结构

有独立的用户空间

task_struct中包含了描述进程和线程的信息

pid_t pid 进程号 最大10亿

volatile long state 进程状态

TASK_RUNNING 准备就绪

TASK_INTERRUPTIBLE 处于等待中 等待条件为真是唤醒,信号/中断也可

TASK_UNINTERRUPTIBLE 条件为真是唤醒,信号/中断不可

TASK_STOPPED 进程中止执行(SIGSTOP/SIGTSTP时进入该状态)

SIGCONT重新回到TASK_RUNNING

TASK_KILLABLE 进程进入睡眠状态 (SIGKILL唤醒)

TASK_TRACED 处于被调试状态

TASK_DEAD 时程退出时

int exit_state 进程退出时的状态

EXIT_ZOMBIE

EXIT_DEAD

struct mm_struct *mm 进程用户空间描述指针

unsigned int policy 进程的调度策略

int prio 优先级

int static_prio 静态优先级

struct sched _rt_entity rt 进间片

current 指针指向当前正在运行的进程的task_struct

进程的创建

vfork() sys_vfork() do_fork() copy_process()

fork() sys_fork() do_fork() copy_process()

进程的销毁

exit() sys_exit() do_exit

2 linux进程调度

调度策略

cfs调度类(kernel/sched_fair.c)

SCHED_NORMAL(SCHED_OTHER) 普通的分时进程

SCHED_BATCH 批处理进程

SCHED_IDLE 只在系统空闲时调度执行

实时调度类(kernel/sched_rt.c)

SCHED_FIFO 先入先出的实时进程

SCHED_RR 时间片轮转的实进进程

调度时机

schedule() 完成调度

示例代码如下:

/*

* schedule() is the main scheduler function.

*/

asmlinkage void __sched schedule(void)

{

struct task_struct *prev, *next;

unsigned long *switch_count;

struct rq *rq;

int cpu;

need_resched:

preempt_disable();

cpu = smp_processor_id();

rq = cpu_rq(cpu);

rcu_sched_qs(cpu);

prev = rq->curr;

switch_count = &prev->nivcsw;

release_kernel_lock(prev);

need_resched_nonpreemptible:

schedule_debug(prev);

if (sched_feat(HRTICK))

hrtick_clear(rq);

spin_lock_irq(&rq->lock);

update_rq_clock(rq);

clear_tsk_need_resched(prev);

if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {

if (unlikely(signal_pending_state(prev->state, prev)))

prev->state = TASK_RUNNING;

else

deactivate_task(rq, prev, 1);

switch_count = &prev->nvcsw;

}

pre_schedule(rq, prev);

if (unlikely(!rq->nr_running))

idle_balance(cpu, rq);

put_prev_task(rq, prev);

next = pick_next_task(rq);

if (likely(prev != next)) {

sched_info_switch(prev, next);

perf_event_task_sched_out(prev, next, cpu);

rq->nr_switches++;

rq->curr = next;

++*switch_count;

context_switch(rq, prev, next); /* unlocks the rq */

/*

* the context switch might have flipped the stack from

under

* us, hence refresh the local variables.

*/

cpu = smp_processor_id();

rq = cpu_rq(cpu);

} else

spin_unlock_irq(&rq->lock);

post_schedule(rq);

if (unlikely(reacquire_kernel_lock(current) < 0))

goto need_resched_nonpreemptible;

preempt_enable_no_resched();

if (need_resched())

goto need_resched;

}

调度的两和方式:

1 主动式:在内核中直接调用schedule()

示例:主动放弃cpu

current->state=TASK_INTERRUPTIBLE;

schedule()l

2 被动式:用户抢占

从系统调用返回用户空间

从中断处理程序返回用户空间

内核抢占

以下情况不允许内核抢占:

内核正在进行中断处理

正在处理中断上下文的bottom half处理

进程正持有spinlock自旋锁、writelock/readlock读写锁

正在执行调度程序schedule

内核抢占计数 preempt_count 在进程的thread_info结构中设置

调度标志 TIF_NEED_RESCHED 表明是否需要重新执行一次调度

在某个进程耗进时间片时

当一个优先级高的进程进入可执行状态时

调度步骤

清理当前运行中的进程

先择下一个要运行的进程 (pick_next_task)

设置新进程的运行环境

进程上下文切换

pick_next_task示例代码如下:

/*

* Pick up the highest-prio task:

*/

static inline struct task_struct *

pick_next_task(struct rq *rq)

{

const struct sched_class *class;

struct task_struct *p;

/*

* Optimization: we know that if all tasks are in

* the fair class we can call that function directly:

*/

/*如果属于公平调度*/

if (likely(rq->nr_running == rq->cfs.nr_running)) {

p = fair_sched_class.pick_next_task(rq);

if (likely(p))

return p;

}

/*属于实时调度*/

class = sched_class_highest;

for ( ; ; ) {

p = class->pick_next_task(rq);

if (p)

return p;

/*

* Will never be NULL as the idle class always

* returns a non-NULL p:

*/

class = class->next;

}

}

3 linux系统调用

用户进程是不能访问内核的,在linux内核中有一组用于实现各种系统功能的

函数,就是系统调用

2.6.32内核中有364个系统调用,其目录arch/arm/include/asm/unistd.h

工作原理:

void main(){

creat("testfile",0666);

}

程序用适当的值填充寄存器 (适当值可以在unistd.h中查找)

调用特殊指令跳转到内核某一固定值,内核根据应用程序所填充的固定值来

找到相应的函数执行

(特殊指令: intel cpu中,由中断0x80实现 arm中是swi/svc

固定的位置:arm中 ENTRY(vector_swi)<entry-common.S>

相应的函数: 内核根据应用程序传递的系统调用号, 从系统调用表中查找相应

的函数

)

示例代码:

create的实现原理

在编译工具链中

/opt/FriendlyARM/toolschain/4.5.1/arm-none-linux-gnueabi/lib

反汇编查看

[root@localhost lib]# arm-linux-objdump -D -S libc.so.6 >log

查看log文件:

部分内容如下:

000b71b0 <creat>:

b71b0:e51fc028 ldrip, [pc, #-40]; b7190

<pipe2+0x20>

b71b4:e79fc00c ldrip, [pc, ip]

b71b8:e33c0000 teqip, #0

b71bc:1a000006 bneb71dc <creat+0x2c>

#程序用适当的值填充寄存器

#在unistd.h中定义如下:

#define __NR_osf_old_creat 8/* not implemented */

b71c0:e1a0c007 movip, r7

b71c4:e3a07008 movr7, #8

#调用特殊指令跳转到内核某一固定值,内核根据应用程序所填充的固定值来

#找到相应的函数执行 calls.s中执行对应系统调用

b71c8:ef000000 svc0x00000000

b71cc:e1a0700c movr7, ip

b71d0:e3700a01 cmnr0, #4096; 0x1000

b71d4:312fff1e bxcclr

b71d8:eafd7a30 b15aa0 <__syscall_error>

b71dc:e92d4083 push{r0, r1, r7, lr}

b71e0:eb006d8f bld2824

<__libc_enable_asynccancel>

b71e4:e1a0c000 movip, r0

b71e8:e8bd0003 pop{r0, r1}

b71ec:e3a07008 movr7, #8

b71f0:ef000000 svc0x00000000

b71f4:e1a07000 movr7, r0

b71f8:e1a0000c movr0, ip

b71fc:eb006db8 bld28e4

<__libc_disable_asynccancel>

b7200:e1a00007 movr0, r7

b7204:e8bd4080 pop{r7, lr}

b7208:e3700a01 cmnr0, #4096; 0x1000

b720c:312fff1e bxcclr

b7210:eafd7a22 b15aa0 <__syscall_error>

b7214:e320f000 nop{0}

b7218:e320f000 nop{0}

b721c:e320f000 nop{0}

添加系统调用:

1 添加新的内核函数

在kernel/sys.c中添加函数

/*asmlinkage 使用栈传递参数

添加到文件最后

*/

asmlinkage int sys_add(int a,int b){

return a+b;

}

2 更新头文件unsitd.h

添加如下代码:

#define __NR_add(__NR_SYSCALL_BASE+365)

3 针对这个新函数更新系统调用表calls.S

添加如下代码:

CALL(sys_add)

4 重新编译内核

5 编写应用程序测试系统调用

示例代码如下:

#include <stdio.h>

#include <linux/unistd.h>

main(){

int result;

result=SYSCALL(361,1,2);

printf("result=",result);

}

4 proc文件系统

proc文件系统是在用户态检查内核状态的机制

查看当前内存使用情况

[root@localhost proc]# cat meminfo

proc文件目录结构

apm 高级电源管理信息

bus 总线以及总线上的设备

devices 可用的设备信息

driver 已经启用的驱动程序

interrupts 中断信息

ioports 端口使用信息

version 内核版本

创建proc文件

struct proc_dir_entry * create_proc_entry(const char *name,mode_t

mode,struct proc_dir_entry *parent)

name 要创建的文件名

mode 要创建的文件属性

parent 这个文件的父目录

创建目录

struct proc_dir_entry *proc_mkdir(const char *name,struct

proc_dir_entry *parent)

name 要创建的文件名

parent:这个目录的父目录

删除目录/文件

void remove_proc_entry(const char *name,struct proc_dir_entry *parent)

name 要删除的文件名或目录名

parent 所在的父目录

读写回调函数

read_proc

int read_func(char *buffer,char **stat,off_toff,int count,int

*peof,void *data)

buffer 返回给用户的信息

stat 一般不使用

off 偏移量

count 用户要取的字节数

peof 读到文件尾时,需将其置1

data 一般不使用

wirte_proc

int write_func(struct file *file,const char *buffer,unsigned long

count,void *data)

file 其proc文件对应的file结构

buffer 待写的数据所在的位置

count 待写数据的大小

data 一般不使用

实现一个proc文件的流程

1 调用create_proc_entry创建一个struc proc_dir_entry

2 对创建的struct proc_dir_entry进行赋值 read_proc mode owner

size write_proc

5 linux内核异常

应用程序中,空指针异常(段错误)

在内核中,如果出现空指针异常,可能会出现死机

oops信息

小于c0000000(3g)

分析步骤

1 错误码原因提示

2 调用栈

3 寄存器

示例代码如下:

oops.c文件

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/init.h>

int D(){

int *p=NULL;

int a=6;

printk("Fuction D\n");

*p=a+5;

}

int C(){

printk("Function C\n");

D();

}

int B(){

printk("Fuction B\n");

C();

}

int A(){

printk("Fuction A\n");

B();

}

int opps_init(){

printk("hi\n");

A();

return 0;

}

void opps_exit(){

}

module_init(opps_init);

module_exit(opps_exit);

Makefile文件如下:

ifeq ($(KERNELRELEASE),)

KERNELDIR ?=/opt/FriendlyARM/mini6410/linux/linux-2.6.38

PWD :=$(shell pwd)

modules:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:

rm -fr *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

*.order Module*

.PHONY: modules modules_install clean

else

obj-m :=oops.o

endif

编译程序:make

将oops.ko文件下载到开发板中

在开发板中加载模块:insmod oops.ko

加载模块提示如下信息:

hi

Fuction A

Fuction B

Function C

Fuction D

#明确出错原因,无法访问空指针

Unable to handle kernel NULL pointer dereference at virtual address

00000000

pgd = cd574000

[00000000] *pgd=5d536831, *pte=00000000, *ppte=00000000

Internal error: Oops: 817 [#1] PREEMPT

last sysfs file: /sys/devices/virtual/vc/vcsa4/dev

Modules linked in: oops(P+) fa_cpu_pfn(P)

CPU: 0 Tainted: P (2.6.38-FriendlyARM #14)

#根据pc寄存器确定出错位置

#出错指令为D函数偏移为0x14的指令

PC is at D+0x14/0x20 [oops]

LR is at D+0xc/0x20 [oops]

#出错地址的指令为bf006014

pc : [<bf006014>] lr : [<bf00600c>] psr: 60000013

sp : cd543ec8 ip : 00002f00 fp : 00000000

r10: 0000001c r9 : 00000022 r8 : 00000000

r7 : bf006068 r6 : 00000001 r5 : 00000000 r4 : bf006128

r3 : 00000000 r2 : 0000000b r1 : bf0060d0 r0 : 0000000d

Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user

Control: 00c5387d Table: 5d574008 DAC: 00000015

Process insmod (pid: 987, stack limit = 0xcd542268)

Stack: (0xcd543ec8 to 0xcd544000)

3ec0: cd543ebc bf006078 cd542000 c01684d8 c07268b4

00000001

3ee0: bf006128 bf006128 bf006170 bf006128 00000000 00000001 cd46e900

00000001

3f00: 0000001c c01ae270 bf006134 c01682b4 c01abf98 c051b76c bf00624c

000afa95

3f20: cd5370a8 d08b2000 00005d80 d08b63ac d08b6260 d08b7ccc cd65b240

00000268

3f40: 000003e8 00000000 00000000 00000020 00000021 00000009 00000000

00000007

3f60: 00000000 00000000 00000000 00000000 00000000 00000000 00000000

c01e0078

3f80: cd4bd9c0 00000001 00000069 beffae34 00000080 c0172788 cd542000

00000000

3fa0: 00000000 c01725e0 00000001 00000069 000bc040 00005d80 000afa95

7fffffff

3fc0: 00000001 00000069 beffae34 00000080 beffae38 000afa95 beffae38

00000000

3fe0: 00000001 beffaadc 0001cb50 401f1664 60000010 000bc040 5fffe821

5fffec21

[<bf006014>] (D+0x14/0x20 [oops]) from [<bf006078>]

(init_module+0x10/0x1c [oops])

[<bf006078>] (init_module+0x10/0x1c [oops]) from [<c01684d8>]

(do_one_initcall+0xbc/0x190)

[<c01684d8>] (do_one_initcall+0xbc/0x190) from [<c01ae270>]

(sys_init_module+0x158c/0x1754)

[<c01ae270>] (sys_init_module+0x158c/0x1754) from [<c01725e0>]

(ret_fast_syscall+0x0/0x30)

Code: e59f0010 eb543a5d e3a03000 e3a0200b (e5832000)

---[ end trace 78b4b200a26b57f5 ]---

Segmentation fault

反汇编部分信息如下:

oops.ko: file format elf32-littlearm

Disassembly of section .text:

00000000 <D>:

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/init.h>

int D(){

0:e92d4008 push{r3, lr}

int *p=NULL;

int a=6;

printk("Fuction D\n");

4:e59f0010 ldrr0, [pc, #16]; 1c <D+0x1c>

8:ebfffffe bl0 <printk>

*p=a+5;

c:e3a03000 movr3, #0

10:e3a0200b movr2, #11

#定位出错位置

#出错指令为D函数偏移为0x14的指令

#Code: e59f0010 eb543a5d e3a03000 e3a0200b (e5832000)

14:e5832000 strr2, [r3]

}

18:e8bd8008 pop{r3, pc}

1c:00000000 andeqr0, r0, r0