linux嵌入式课程设计源代码分析.docx
《linux嵌入式课程设计源代码分析.docx》由会员分享,可在线阅读,更多相关《linux嵌入式课程设计源代码分析.docx(24页珍藏版)》请在冰豆网上搜索。
linux嵌入式课程设计源代码分析
1.进程的概念1
2.进程切换的三个层次1
3.Linux中进程的特点1
4.taskstruct2
5.进程之间的状态转换的系统调用5
6.进程的创建:
TASK_RUNNING5
7.进程的调度(schedule())8
8.进程转换9
9.Semaphores(信号灯)13
10.锁机制18
11.管道(流)21
12.进程的终止23
13.总结26
14.参考文献26
1.进程的概念
进程执行操作系统的任务。
程序是存放在磁盘上的包括一系列机器代码指令和数据的可执行的映像,因此是一个被动的实体。
进程可以看作是一个执行中的计算机程序。
它是动态的实体,在处理机执行机器代码时不断改变。
进程包括处理程序的指令和数据,以及程序计数器、其他CPU的
寄存器和包括临时数据(例如:
例程参数、返回地址和保存的变量)的堆栈。
当前执行的程序,或者说进程,包括微处理器中所有的当前活动。
进程是操作系统的最小调度单位。
2.进程切换的三个层次
(1)用户数据的保存:
包括正文段(TEXT),数据段(DATA,BSS),栈段(STACK),共享内存段(SHAREDMEMORY保存。
(2)寄存器数据的保存:
包括PC(programcounter,指向下一条要执行
的指令的地址),PSW(processorstatusword,处理机状态字),SP(stack
pointer,栈指针),PCBP(pointerofprocesscontrolblock,进程控制块指针),FP(framepointer,指向栈中一个函数的local变量的首地址),AP(augumentpointer,指向栈中函数调用的实参位置),ISP(interruptstackpointer,中断栈指针),以及其他的通用寄存器等。
(3)系统层次的保存:
包括proc,u,虚拟存储空间管理表格,中断处理栈。
以便于该进程再一次得到CPU时间片时能正常运行下去。
3.linux中进程的特点
Linux是一个多进程的操作系统,进程是分离的任务,拥有各自的权利和责任。
如果一个进程崩溃,它不应该让系统的另一个进程崩溃。
每一个独立的进程运行在自己的虚拟地址空间,除了通过安全的核心管理的机制之外无法影响其他的进程。
在一个进程的生命周期中,进程会使用许多系统资源。
比如利用系统的
CPU执行它的指令,用系统的物理内存来存储它和它的数据。
它会打开和使用
文件系统中的文件,会直接或者间接使用系统的物理设备。
如果一个进程独
占了系统的大部分物理内存和CPU对于其他进程就是不公平的。
所以Linux
必须跟踪进程本身和它使用的系统资源以便公平地管理系统中的进程
4.taskstruct
Linux中,每个进程用一个task_struct的数据结构来表示,用来管统中的进程。
task_struct结构中有关于进程调度的两个重要的数据项:
structtask_struct{
volatilelongstate;
*/
unsignedlongflags;
/*-1unrunnable,0runnable,>0stopped
/*perprocessflags,definedbelow*/
};
每个在Task向量表中登记的进程都有相应的进程状态和进程标志,是进行进程调度的重要依据。
进程在执行了相应的进程调度操作后,会由于某些原因改变自身的状态和标志,也就是改变state和flags这两个数据项进程的状态不同、标志位不同对应了进程可以执行不同操作。
//进程状态
#defineTASK_RUNNING0
#defineTASK_INTERRUPTIBLE1
#defineTASK_UNINTERRUPTIBLE2
#defineTASK_ZOMBIE4
#defineTASK_STOPPED8
#defineTASK_SWAPPING16
它们的含义分别是:
TASK_RUNNINGE在运行的进程(是系统的当前进程)或准备运行的进程(在Running队列中,等待被安排到系统的CPU。
处于该状态的进程实际参与了进程调度。
TASK_INTERRUPTIBL处于等待队列中的进程,待资源有效时唤醒,也可由其它进程被信号中断、唤醒后进入就绪状态。
TASK_UNINTERRUPTIB处于等待队列中的进程,直接等待硬件条件,待资源有效时唤醒,不可由其它进程通过信号中断、唤醒。
TASK_ZOMBIE终止的进程,是进程结束运行前的一个过度状态(僵死状态)。
虽然此时已经释放了内存、文件等资源,但是在Task向量表中仍有一
个task_struct数据结构项。
它不进行任何调度或状态转换,等待父进程将它彻底释放。
TASK_STOPPEDft程被暂停,通过其它进程的信号才能唤醒。
正在调试的进程可以在该停止状态。
TASK_SWAPPIN进程页面被兑换出内存的进程。
这个状态基本上没有用到,只有在sched.c的count_active_tasks()函数中判断处于该种状态的进程也属于active的进程,但没有对该状态的赋值。
//进程标志位:
#definePF_ALIGNWARN0x00000001
#definePF_STARTING0x00000002
#definePF_EXITING0x00000004
#definePF_PTRACED0x00000010
#definePF_TRACESYS0x00000020#definePF_FORKNOEXEC0x00000040
#definePF_SUPERPRIV0x00000100
#definePF_DUMPCORE0x00000200
#definePF_SIGNALED0x00000400
#definePF_MEMALLOC0x00000800
#definePF_VFORK0x00001000
#definePF_USEDFPU0x00100000
#definePF_DTRACE0x00200000
各个标志位的代表着不同含义,对应着不同调用:
1、
PF_STARTING
进程正被创建
2、
PF_EXITING
标志进程开始关闭。
3、
PF_PTRACED
进程被跟踪标志,
4、
PF_TRACESYS
正在跟踪系统调用。
5、
PF_FORKNOEXEC进程刚创建,但还没执行
p->flags&=~PF_FORKNOEXEC
6、
PF_SUPERPRIV
超级用户特权标志。
如果是超级用户进程则置位,用户特权设为超级用户,如是超级用户,在统计时置统计标志(accountingflag)为ASU。
7、PF_DUMPCORE标志进程是否清空core文件。
10、PF_DTRACE进程延期跟踪标志,只在m68k下使用。
11、PF_ONSIGSTK标志进程是否工作在信号栈,只在m68k
方式下使用。
5.进程之间的状态转换的系统调用
我将参与Linux的各进程之间的状态转换的系统调用总结成一张流程图:
syscall_tracedo_signal()
ii
i:
I1TASKINTERRUPTIB^AITINGSTATUSTASKuninterruptible
6.进程的创建:
TASK_RUNNING
第一个进程在系统启动时创建,当系统启动的时候它运行在核心态,这时,只有一个进程:
初始化进程。
象所有其他进程一样,初始进程有一组用堆栈、寄存器等等表示的机器状态。
当系统中的其他进程创建和运行的时候这些信息存在初始进程的task_struct数据结构中。
在系统初始化结束的时候,初始进程启动一个核心进程(叫做init)然后执行空闲循环,什么也不做。
当没有什么可以做的时候,调度程序会运行这个空闲的进程。
这个空闲进程的task_struct是唯一个不是动态分配而是在核心连接的时候静态定义的,为了不至于混淆,叫做init_task。
进程由do_fork()函数创建,先申请空间,申请核心堆栈;然后在Task向量表中找到空闲位置;在进行正式初始化以前,将新创建的进程的状态都置为TASK_UNINTERRUPTIBL以免初始化过程被打断;开始初始化工作,如初始化进程时钟、信号、时间等数据;继承父进程的资源,如文件、信号量、内存等;完成进程初始化后,由父进程调用wake_up_process()函数将其唤醒,状态变为TASK_RUNNINGt到就绪队列runqueue,返回子进程的pid。
//C:
\SRCLNX\KERNEL\FORK.C
intdo_fork(unsignedlongclone_flags,unsignedlongusp,structpt_regs*regs)
{
为新进程申请PCB空间;
if(申请不到)
返回错误,退出;
为新进程申请核心堆栈;
if(核心堆栈申请不到)
返回错误,退出;
为新进程在Task向量表中找到空闲位置;
/*复制父进程currentPCB中的信息,继承current的资源*/;
p=current;
在进行正式初始化以前,将新创建的进程的状态都置为
TASK_UNINTERRUPTIBL以免初始化过程被打断,并置一些标志位.
/*为防止信号、定时中断误唤醒未创建完毕的进程,将子进程的状态设成不可中断的*/
p->state=TASK_UNINTERRUPTIBLE;
/*跟踪状态和超级用户特权是没有继承性的,因为在root用户为普通用户创建进程时,出于安全考虑这个普通用户的进程不允许拥有超级用户特权。
*/
p->flags&=~(PF_PTRACED|PF_TRACESYS|PF_SUPERPRIV);
/*将进程标志设成初建,在进程第一次获得CPU时,内核将根据此标志进行一定操作*/
p->flags|=PF_FORKNOEXEC;
开始Task_struct的初始化工作,如初始化进程时钟、信号、时间等数据;
继承父进程所有资源:
拷贝父进程当前打开的文件;
拷贝父进程在VFS的位置;
拷贝父进程的信号量;
拷贝父进程运行的内存;
拷贝父进程的线程;
初始化工作结束,父进程将其将其唤醒,挂入running队列中,返回子进程的pid;
}
7.进程的调度(schedule())
处于TASK_RUNNIN状态的进程移到runqueue,会由schedule。
按CPU调度算法在合适的时候选中,分配给CPU。
新创建的进程都是处于TASK_RUNNING态,而且被挂到runqueue的队首。
进程调度采用变形的轮转法(roundrobin)。
当时间片到时(10ms的整数倍),由时钟中断引起新一轮调度,把当前进程挂到runqueue队尾
调度程序schedule()从核心的多个地方运行。
它可以在把当前进程放到等待队列之后运行,也可以在系统调用之后进程从系统态返回进程态之前运行。
需要运行调度程序的另一个原因是系统时钟刚好把当前进程的计数器(counter)置成了0。
每一次调度程序运行它做以下工作:
(1)kernelwork调度程序运行bottomhalfhandler并处理系统的调度任务队列。
(2)Currentpocess在选择另一个进程之前必须处理当前进程。
(3)如果当前进程的调度策略是环则它放到运行队列的最后。
(4)如果任务状态是TASK_INTERRUPTIBL的而且它上次调度的时候收到过一个信号,它的状态变为TASK_RUNNING;
如果当前进程超时,它的状态成为RUNNING;
如果当前进程的状态为RUNNIN则保持此状态;
不是RUNNING或者INTERRUPTIBL的进程被从运行队列中删除。
这意味着当调度程序查找最值得运行的进程时不会考虑这样的进程。
(5)ProcessSelection调度程序查看运行队列中的进程,查找最值得运行的进程。
如果有实时的进程(具有实时调度策略),就会比普通进程更重一些。
(6)SwapProcesses如果最值得运行的进程不是当前进程,当前进程必须被挂起,运行新的进程。
(7)交换出去进程的上下文发生在调度的最后。
前一个进程存储的上下文,就是当这个进程在调度结束的时候系统的硬件上下文的快照。
相同的,当加载新的进程的上下文时,仍旧是调度结束时的快照,包括进程的程序计数器和寄存器的内容。
(8)如果前一个进程或者新的当前进程使用虚拟内存,则系统的页表需要更新。
同样,这个动作适合体系结构相关。
AlphaAXP处理器,使用TLT(Translation
Look-asideTable)或者缓存的页表条目,必须清除属于前一个进程的缓存的页表条目。
8.进程转换
在TASK_RUNNING及TASK_UNINTERRUPTIBLEASK_INTERRUPTIB之间
进程创建以后到被杀死的整个进程生命周期中,状态可能在TASK_RUNNING
TASK_INTERRUPTIBLErASK_UNINTERRUPTIBLErASK_STOPPED及TASK_ZOMBLE之间转换的。
1.通过sleep_on()、interruptible_sleep_on()、sleep_on_timeout()、interruptible_sleep_on_timeout()以及wake_up()、wake_up_process()、wake_up_interruptible()函数对进行的转换:
sleep_on():
TASK_RUNNING->TASK_UNINTERRUPTIBLE
一般来说引起状态变成TASK_UNINTERRUPTIB的资源申请都是对一些硬件资
源的申请,如果得不到这些资源,进程将不能执行下去,不能由signal信号或时钟中断唤醒,而回到TASK_RUNNIN状态。
这种类型的转换原因有:
(1)对某些资源的操作只能由一个进程进行,所以系统对该项资源采用上锁机制。
在申请该项资源时,必须先申请资源的锁,如果已经被别的进程占用,则必须睡眠在对该锁的等待队列上。
而且这种睡眠不能被中断,必须等到得到了资源才能继续进行下去。
(2)某些进程在大部分时间处于睡眠状态,仅在需要时被唤醒去执行相应的操作,当执行完后,该进程又强制去睡眠。
wake_up():
TASK_UNINTERRUPTIBLE->TASK_RUNNING;
TASK_INTERRUPTIBLE->TASK_RUNNING
处于TASK_UNINTERRUPTIB状态的进程不能由signal信号或时钟中断唤醒,只能由wake_up()或wake_up_process()唤醒。
wake_up()函数的作用是将wait_queue中的所有状态为TASK_INTERRUPTIBLETASK_UNINTERRUPTIB的进程状态都置为TASK_RUNNIN并将它们都放到running队列中去,即唤醒了所有等待在该队列上的进程。
voidwake_up(structwait_queue**q)
{
structwait_queue*next;
structwait_queue*head;
if(!
q||!
(next=*q))
return;
head=WAIT_QUEUE_HEAD(q);
while(next!
=head){
structtask_struct*p=next->task;
next=next->next;
if(p!
=NULL){
if((p->state==TASK_UNINTERRUPTIBLE)||
(p->state==TASK_INTERRUPTIBLE))
wake_up_process(p);
}
if(!
next)
gotobad;
}
return;
bad:
printk("wait_queueisbad(eip=%p)\n",__builtin_return_address(0));
printk("q=%p\n",q);
printk("*q=%p\n",*q);
}
voidwake_up_process(structtask_struct*p){
unsignedlongflags;
/*
*Wewantthecommoncasefallthroughstraight,thusthegoto.*/
spin_lock_irqsave(&runqueue_lock,flags);p->state=TASK_RUNNING;
if(p->next_run)
gotoout;
add_to_runqueue(p);
spin_unlock_irqrestore(&runqueue_lock,flags);
reschedule_idle(p);
return;
out:
spin_unlock_irqrestore(&runqueue_lock,flags);
}
这个函数的实现机制与wake_up()的不同在于,它只能唤醒某一个特定的睡眠进程,而wake_up()是唤醒整个等待队列的睡眠进程。
所以,它的唤醒的原因与wake_up()也有一定的区别,除了由于wake_up()对它的调用之外,它唤醒进程并不是由于资源有效造成的,唤醒的进程也不是因等待资源有效而睡眠的进程。
有以下几种情况:
(1)父进程对子进程的唤醒:
如:
在sys_ptrace()中当收到的跟踪请求为:
PTRACE_CON在处理完信号后继续);PTRACE_KILL将子进程杀出);PTRACE_SINGLESTE对子进程进行单步跟踪);PTRACE_DETACH寸候,都会在处理结束时,唤醒子进程,给子进程一个运行的机会。
在do_fork()中,新建进程初始化完毕,会由父进程唤醒它,将该进
程移到runqueue中,置状态为TASK_RUNNING
(2)当需要的时候唤醒某个睡眠的系统调用,进行处理:
3)收到信号所进行的相应处理:
(4)资源有效时,wake_up()对整个等待队列的唤醒是通过对每个等待队列上的进程调用wake_up_process()实现的。
9.Semaphores(信号灯)
信号量用于生成锁机制,避免发生数据不一致。
信号量最简单的形式就是内存中一个位置,它的取值可以由多个进程检验和设置。
检验和设置的操作,至少对于关联的每一个进程来讲,是不可中断或者说有原子性:
只要启动就不能中止。
检验和设置操作的结果是信号灯当前值和设置值的和,可以是正或者负。
根据测试和设置操作的结果,一个进程可能必须睡眠直到信号灯的值被另一个进程改变。
信号灯可以用于实现临界区域(criticalregions),就是重要的代码区,同一时刻只能有一个进程运行。
对信号灯的操作是通过以下两组基本函数实现的:
1.void__up(structsemaphore*sem):
TASK_UNINTERRUPTIBLE->TASK_RUNNING;
TASK_INTERRUPTIBLE->TASK_RUNNING
int__do_down(structsemaphore*sem,inttask_state)由以下两个函数
调用,分别转换到不同的等待状态:
(1)int__down_interruptible(structsemaphore*sem):
TASK_RUNNING->TASK_INTERRUPTIBLE;
(2)void__down(structsemaphore*sem):
TASK_RUNNING->TASK_UNINTERRUPT;IBLE
2.externinlinevoidup(structsemaphore*sem)
externinlinevoiddown(structsemaphore*sem);
externinlineintdown_interruptible(structsemaphore*sem)
Linux信号量是通过两路counter变量实现的:
当进程由于申请不到临界区资源而睡眠时,会将semaphore结构中的”ount”变量值原子地递减1,进程睡眠等待临界区资源的释放;而当up()函数唤醒睡眠等待进程时,如果”count”变量值小于0,会将semaphore结构中的”waking”变量值原子地递增1,唤醒睡眠进程。
虽然所有等待进程都被唤醒。
但只有首先得到”waking”的进程才能得到信号量,继续运行下去,其他进程仍然回到最初的等待状态。
Linux定义信号灯结构是:
structsemaphore{
atomic_tcount;
intwaking;
structwait_queue*wait;
};
信号灯的值初始化为一个宏定义的结构MUTEXS值{count=1,waking=0,
wait=NULL}。
void__up(structsemaphore*sem)
占有临界区资源的进程,调用__up()释放资源。
在__up()函数中,调用
wake_one_more()函数,原子地读sem->count,如果sem->count<=0,则sem->waking++,并唤醒所有等待在该sem-->wait上的进程。
void__up(structsemaphore*sem){
wake_one_more(sem);
wake_up(&sem->wait);
}
int__do_down(structsemaphore*sem,inttask_state):
申请临界区资源的进程会通过调用__do_down()来竞争资源。
在__do_down()函数中,调用waking_non_zero(structsemaphore*sem)或waking_non_zero_interruptible(structsemaphore*sem)抢占临界区资源,如果抢占到,则将当前进程置为TASK_RUNNIN否则将当前进程的状态置为task_state,并处于循环等待状态。
进程通过waking_non_zero()来竞争临界区资