wenjs00 2013-03-28
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_creat8/* 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