都说这个主题不错,连我自己都认为有点过大了,不过我想我还是得坚持下去,努力在有限的时间里学习到Linux内核的奥秘,也希望你们多赐教,让我更有进步。明天讲的全是进程,这点在大二的时侯就困扰了我,结果那种时侯我就止步不前了,这儿主要讲的是为什么引入进程、进程在Linux空间是怎样实现的,但是描述了所有与进程执行相关的数据结构arm linux内核中arm中断实现详解,最后都会提到异常和中断等异步执行流程,它们是怎么和Linux内核进行交互的,下边我就来具体介绍一下进程的奥妙。
首先我们要明晰一个概念,我们说的程序是指由一组函数组成的可执行文件,而进程则是特定程序的个体化实例sogou pinyin linux,进程是对硬件所提供资源进行操作的基本单位。在我们继续讨论进程之前,得明白一个几个命名习惯,一般说的“任务“和”进程“就是一回事。
事实上,进程都有一个生命周期linux查看磁盘空间,进程从创建之后会经历各类状态后死亡,下边的事例帮助你们理解一下程序是怎样实例化进程的。
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
int pid;
pid = fork();
if(pid == 0)
{
execle("/bin/ls", NULL);
exit(2);
}
if(waitpid(pid) <0 )
printf("wait errorn");
pid = fork();
if(pid == 0)
{
fd = open("Chapter_2.txt",O_RDONLY);
close(fd);
}
if(waitpid(pid)<0)
printf("wait errorn");
exit(0);
}
creat_process
一个进程包括了好多属性,使进程彼此互不相同,在内核中,进程描述符是一个task_struct的结构体,拿来保存进程的属性和相关信息,内核使用循环单向数组task_list储存所有进程描述符,同时利用全局变量current保存当前运行进程的task_struct。至于task_struct的定义你们可以参见include/Linux/sched.h这儿我讲不了辣么多,不过我得说明一下进程和线程的区别,进程由一个或则多个线程组成,每位线程对应一个task_struct,其中包含一个惟一的线程ID。线程作为调度和分配的基本单位,而进程作为拥有资源的基本单位;除了进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行;进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
进程描述符(task_struct)个别数组涵义,这儿有太多的与进程相关的域,我列举一些如下,,假定进程为P。
我们了解到,任何进程都是由别的进程创建的,操作系统通过fork()、vfork()、clone()系统调用来完成进程的创建。进程创建的系统调用如右图:
这三个系统最终都调用了do_fork()函数arm linux内核中arm中断实现详解,do_fork()是内核函数,它完成与进程创建有关的大部份工作,下边我来简略介绍一下fork()、vfork()、clone()函数。
fork()函数
fork()函数返回两次,一次是子进程,返回值为0;一次是父进程,将返回子进程的PID,
vfork()函数
和fork()函数类似,而且后者的父进程仍然阻塞,直至子进程调用exit()或exec()后。
clone()函数
clone()函数接受一个指向函数的表针和该函数的参数,由do_fork()创建的子进程一诞生就调用这个库函数。
二者的惟一区别,在最终调用do_fork()函数设置的这些标志不一样,如下表。
fork()
vfork()
clone
SIGCHLD
CLONE_VFORK
CLONE_VM
do_fork()函数借助辅助函数copy_process()来创建进程描述符以及子进程执行所须要的所有其他内核数据结构,在Linux内核中,供用户创建进程的系统调用fork()函数的响应函数是sys_fork()、sys_clone()、sys_vfork()。这三个函数都是通过调用内核函数do_fork()来实现的。下边就具体的do_fork()函数程序代码进行剖析(该代码坐落kernel/fork.c文件中)
fork.c
int do_fork(unsigned long clone_flags,unsigned long stack_start, struct pt_regs *regs,
unsigned long stack_size)
{
int retval;
struct task_struct *p;
struct completion vfork;
retval = -EPERM ;
if ( clone_flags & CLONE_PID )
{
if ( current->pid )
goto fork_out;
}
reval = -ENOMEM ;
p = alloc_task_struct(); // 分配内存建立新进程的 task_struct 结构
if ( !p )
goto fork_out;
*p = *current ; //将当前进程的 task_struct 结构的内容复制给新进程的 PCB结构
retval = -EAGAIN;
//下面代码对父、子进程 task_struct 结构中不同值的数据成员进行赋值
if ( atomic_read ( &p->user->processes ) >= p->rlim[RLIMIT_NPROC].rlim_cur
&& !capable( CAP_SYS_ADMIN ) && !capable( CAP_SYS_RESOURCE ))
goto bad_fork_free;
atomic_inc ( &p->user->__count); //count 计数器加 1
atomic_inc ( &p->user->processes); //进程数加 1
if ( nr_threads >= max_threads )
goto bad_fork_cleanup_count ;
get_exec_domain( p->exec_domain );
if ( p->binfmt && p->binfmt->module )
__MOD_INC_USE_COUNT( p->binfmt->module ); //可执行文件 binfmt 结构共享计数 + 1
p->did_exec = 0 ; //进程未执行
p->swappable = 0 ; //进程不可换出
p->state = TASK_UNINTERRUPTIBLE ; //置进程状态
copy_flags( clone_flags,p ); //拷贝进程标志位
p->pid = get_pid( clone_flags ); //为新进程分配进程标志号
p->run_list.next = NULL ;
p->run_list.prev = NULL ;
p->run_list.cptr = NULL ;
init_waitqueue_head( &p->wait_childexit ); //初始化 wait_childexit 队列
p->vfork_done = NULL ;
if ( clone_flags & CLONE_VFORK ) {
p->vfork_done = &vfork ;
init_completion(&vfork) ;
}
spin_lock_init( &p->alloc_lock );
p->sigpending = 0 ;
init_sigpending( &p->pending );
p->it_real_value = p->it_virt_value = p->it_prof_value = 0 ; //初始化时间数据成员
p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0 ; //初始化定时器结构
init_timer( &p->real_timer );
p->real_timer.data = (unsigned long)p;
p->leader = 0 ;
p->tty_old_pgrp = 0 ;
p->times.tms_utime = p->times.tms_stime = 0 ; //初始化进程的各种运行时间
p->times.tms_cutime = p->times.tms_cstime = 0 ;
#ifdef CONFIG_SMP //初始化对称处理器成员
{
int i;
p->cpus_runnable = ~0UL;
p->processor = current->processor ;
for( i = 0 ; i < smp_num_cpus ; i++ )
p->per_cpu_utime[ i ] = p->per_cpu_stime[ i ] = 0;
spin_lock_init ( &p->sigmask_lock );
}
#endif
p->lock_depth = -1 ; // 注意:这里 -1 代表 no ,表示在上下文切换时,内核不上锁
p->start_time = jiffies ; // 设置进程的起始时间
INIT_LIST_HEAD ( &p->local_pages );
retval = -ENOMEM ;
if ( copy_files ( clone_flags , p )) //拷贝父进程的 files 指针,共享父进程已打开的文件
goto bad_fork_cleanup ;
if ( copy_fs ( clone_flags , p )) //拷贝父进程的 fs 指针,共享父进程文件系统
goto bad_fork_cleanup_files ;
if ( copy_sighand ( clone_flags , p )) //子进程共享父进程的信号处理函数指针
goto bad_fork_cleanup_fs ;
if ( copy_mm ( clone_flags , p ))
goto bad_fork_cleanup_mm ; //拷贝父进程的 mm 信息,共享存储管理信息
retval = copy_thread( 0 , clone_flags , stack_start, stack_size , p regs );
//初始化 TSS、LDT以及GDT项
if ( retval )
goto bad_fork_cleanup_mm ;
p->semundo = NULL ; //初始化信号量成员
p->prent_exec_id = p-self_exec_id ;
p->swappable = 1 ; //进程占用的内存页面可换出
p->exit_signal = clone_flag & CSIGNAL ;
p->pdeatch_signal = 0 ; //注意:这里是父进程消亡后发送的信号
p->counter = (current->counter + 1) >> 1 ;//进程动态优先级,这里设置成父进程的一半,应注意的是,这里是采用位操作来实现的。
current->counter >> =1;
if ( !current->counter )
current->need_resched = 1 ; //置位重新调度标记,实际上从这个地方开始,分裂成了父子两个进程。
retval = p->pid ;
p->tpid = retval ;
INIT_LIST_HEAD( &p->thread_group );
write_lock_irq( &tasklist_lock );
p->p_opptr = current->p_opptr ;
p->p_pptr = current->p_pptr ;
if ( !( clone_flags & (CLONE_PARENT | CLONE_THREAD ))) {
p->opptr = current ;
if ( !(p->ptrace & PT_PTRACED) )
p->p_pptr = current ;
}
if ( clone_flags & CLONE_THREAD ){
p->tpid = current->tpid ;
list_add ( &p->thread_group,¤t->thread_group );
}
SET_LINKS(p);
hash_pid(p);
nr_threads++;
write_unlock_irq( &tasklist_lock );
if ( p->ptrace & PT_PTRACED )
send_sig( SIGSTOP , p ,1 );
wake_up_process(p); //把新进程加入运行队列,并启动调度程序重新调度,使新进程获得运行机会
++total_forks ;
if ( clone_flags & CLONE_VFRK )
wait_for_completion(&vfork);
//以下是出错处理部分
fork_out:
return retval;
bad_fork_cleanup_mm:
exit_mm(p);
bad_fork_cleanup_sighand:
exit_sighand(p);
bad_fork_cleanup_fs:
exit_fs(p);
bad_fork_cleanup_files:
exit_files(p);
bad_fork_cleanup:
put_exec_domain( p->exec_domain );
if ( p->binfmt && p->binfmt->module )
__MOD_DEC_USE_COUNT( p->binfmt->module );
bad_fork_cleanup_count:
atomic_dec( &p->user->processes );
free_uid ( p->user );
bad_fork_free:
free_task_struct(p);
goto fork_out;
}
fork
Linux中的进程有7种状态,进程的task_struct结构的state数组指明了该进程的状态。右图形象的形容了各个状态之间的转换,这儿不多加诠释,你们看图感受。
至于进程的中止,上文早已提及过了exit()函数,进程中止有三种形式:明晰而自愿的中止,蕴涵但也是自愿中止,自然而然的运行中止,那些可以通过sys_exit()函数、do_exit()函数来实现,这儿不多说了,都挺好懂的,到此,我们应当对进程在生命周期中所经历的各类状态,完成状态转换的大部份函数等等等有了了解了,有须要补充的或则不懂再借书i些资料就应当才能对进程的相关知识有了挺好的把握了,希望你们就能理解,这么我的任务也算完成了一半了。
了解了以进程为中心的状态和转换并且要真正完成进程的运行和中止,这么内核的基本框架是必需要把握的,如今我们来介绍调度程序的基础知识,调度程序的对象是一个称为运行队列的结构,右图说明了队列中的优先权字段,其定义以及相关剖析如下:
struct prio_array {
int nr_active; //计数器,记录优先权数组中的进程数
unsigned long bitmap[BITMAP_SIZE]; //bitmap是记录数组中的优先权,实际长度取决于系统无符号长整型的大小
struct list_head queue[MAX_PRIO]; //queue存储进程链表的数组,且每个链表含有特定优先权的进程
};
最后提到的是异步执行流程,我们说过,进程才能通过终端中断一个状态转换到另一个状态,获得这些转换的惟一途径就包括异常和中断在内的异步。(这儿吐槽一下,虽然这个时侯我好冷了,认为好难写,都怪大二时侯基础不好,如今一年过去了,大三狗假期大阴天不出去逛,待在实验室里,不过这个时侯符合主题,耳朵瓜中断了一下)
异常:
异常也称作同步中断,是发生在整个处理器硬件内部的风波。异常一般发生在指令执行以后。大多数现代处理器容许程序员通过执行个别指令来形成一个异常。其中一个反例就是系统调用。
系统调用:
用户态的程序调用的许多C库类库,就是把代码和一个或则多个系统调用捆绑在一起产生一个单独的函数。当用户进程调用其中一个函数的时侯,某个值被装入适当的处理器寄存器中,并形成一个软中断irp(异常)。之后这个软中断调用内核入口点。系统调用才能在用户空间和内核空间之间传递数据,由两个内核函数来完成这个任务:copy_to_user()和copy_from_user()。系统调用号和所有的参数都先被存入处理器的寄存器中,当x86的异常处理程序处理软中断0x80时,它对系统调用表进行索引。
中断:
中断对处理器的执行是异步的,就是说中断才能早指令之间发生。通常要发生中断,中断控制器是必须的(x86用的是8259中断处理器)。当中断处理器有有一个待处理的中断时,它就触发联接到处理器的相应INT线,之后处理器通过触发线来确认这个讯号,确认线联接到INTA线上。这时侯,中断处理器就可以把IRQ数据传到处理器上了,这就是一个中断确认周期。具体的反例就不好列出了,须要太大篇幅,也须要更多的知识就能去深刻了解。
IRQ结构:
小结:
三天的时间,全在进程上面,明天主要是解释了为什么引入进程,简单讨论了用户空间与内核空间的控制流,但是讨论了进程在内核中是怎样实现的,上面涉及到队列的知识,本问没有提到,就须要读者自己去学习数据结构,其实Linux内核须要挺好的数据结构知识,最后还简略囊括了终端异常,再者,觉得进程是个大腿骨,讲的很宽泛,还须要大量时间去学习,但是剖析Linux内核源代码,其实,继续加油~