进程管理

一. 进程

       程序:完成特定任务的一系列指令的集合

       进程:一段程序的执行过程。

                1) 进程不仅包含程序代码,还包含它所占用的资源:open files,pending signals,kernel data,process state,memory 和 data section等。

                2) 每一个进程都有自己独立的地址空间和执行状态。

                3) 进程是资源分配的最小单位。

       线程:进程中的一个执行流。

                1) 线程是CPU调度的最小单位。

                2) 每一个线程包含独立的程序计数器,进程栈,一组寄存器。线程组中的所有线程与进程共享同一虚拟地址空间。

        **在Linux中,不同于其他类型的操作系统,不对进程和线程作区分,线程只是一种特殊的进程。

二. 进程描述符

       task list:内核将所有的进程存储在一个环形的双向链表中,即task list。

       进程描述符:1) task_struct:进程描述符用于保存一个进程所包含的所有信息,包括open files、process’s address space等。

                             2) 在Linux 2.6中,task_struct存储在每个进程的kernel stack的end处,这样方便计算task_struct的位置,从而获取进程的信息;

                                    之后通过新的数据结构thread_info,其同样保存在kernel stack的底部,该结构中保存有task_struct指针。

                             3) 在Linux中,可以通过宏current方便地得到当前正在运行的进程。

三.进程状态

       进程状态存储在进程描述符task_struct中的state域。五个状态:

       1) TASK_RUNNING: 正在运行或者在runqueue中等待运行。

       2) TASK_INTERRUPTIBLE:进程处于睡眠状态,等待某个条件存在即进入TASK_RUNNING.

       3) TASK_UNINTERRUPTIBLE:深度睡眠,信号来了也无法醒来,只能被wake_up()唤醒。

       4) TASK_ZOMBIE:僵死进程。

       5) TASK_STOPPED:进程停止运行,task收到信号(SIGSTOP,SIGTSTP,SIGTTIN等)或者在debug的过程中收到任何信号。

            

四.进程上下文

              进程中最重要的部分就是可执行的程序代码,这些代码从可执行文件中读取,并在该程序的地址空间执行。通常程序运行在

       用户空间,当程序执行系统调用或者发生exception的时候,会进入内核空间,此时内核即处于进程上下文。


五.进程家族树

              在Linux中,所有的进程都是init进程(pid = 0)的子孙,每个进程有且只能有一个父进程,同时每个进程可以有很多个子进程,

        属于同一个父进程的进程为siblings,每个进程的task_struct中都保留了指向父进程的指针parent和指向子进程的指针children。


六.进程的创建

              Linux通过clone()系统调用来实现的fork(), clone() 在内核中调用是的do_fork()函数, 而do_fork()又调用了copy_process()来forking process,

       copy_process()的流程如下:

            1) call dup_task_struct,这个创建一个kernel stack/thread_info/task_struct给新的process; 这些结构的值与父进程完全相同。

              2) 检测新的子进程的资源是否超过了当前user的资源限制;

            3) 将child和parent区分开,将process descriptor的部分成员清空或设置初始值;

            4)child的state被置为TASK_UNINTERRUPTIBLE,来保证child还不被运行;

            5)设置child的task_struct的flags成员,标示是超级用户权限的PF_SUPERPRIV被清空,标示还未调用exec()的PF_FORKNOEXEC被置上

            6)通过alloc_pid()给pid成员赋值;

            7)根据传给clone()的参数来决定是否赋值open files/file system info/signal handlers/process address space/name space

            8)最后copy_process返回一个指向新child的指针给caller


       **COW机制:

             在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制“技术,
        也就是只有进程空间的各段的内容要发生变化时,才会将父进程的相应的内容复制一份给子进程。
             在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟
        空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给
        子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相
        同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

七.Linux线程  

       Linux实现thread的方法比较特别,在linux内核中并没有thread这么一个概念,所有的线程在Linux内核中被看做是标准进程,Linux Kernel并不提供针对线程的调度,取而代之的是,在Linux中线程仅仅是一个与其他进程共享某些特定资源的进程,每个线程有独立的task_struct。

      Linux的线程是由带有如下参数的clone()系统调用创建的:

     clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

      参数的含义:

      CLONE_VM: Parent and child share address space.

     CLONE_FS: Parent and child share filesystem information

      CLONE_FILES: Parent and child share open files.

      CLONE_SIGHAND: Parent and child share signal handlers and blocked


八.进程终结

      一个进程的结束,将在内核中调起do_exit()函数,流程如下:

      1) 将current->flag的PF_EXITING置上;

      2) 调用del_timer_sync()来取消内核timer,当此函数返回时,可以保证没有timer handler在运行以及没有timer在queue中

      3) 如果BSD accounting is enable, 则调用acct_process()来写accounting info

      4) 调用__exit_mm()来释放被进程占用的mm_struct,如果没有其他进程(线程)在使用这个内存空间,则deallocate

      5) 调用exit_sem().若process有在queue中等待的信号量(sem),则在这里将其dequeue

      6) 调用__exit_files(), __exit_fs(), exit_namespace(), exit_sighand()用来减少对文件操作符以及filesystem data,process namespace,signal handler的

            引用计数;如果有引用计数为0,则这个对象没有被任何process使用,于是就将其移除

      7)随后,current->exit_code被设置,用于之后parent取得该值

      8)之后,调用exit_notify()来发送一个信号给parent,同时将current->state置为TASK_ZOMBIE

      9)最后,调用 schedule()将当前进程换出


九.进程描述符的移除

            当do_exit()调用之后,task_struct实际上还是存在的,这个重要是要等parent取得有关child process的相关信息,当完成之后,wait4()系统调用将被调
     起,之后在内核中release_task()函数将被调起,流程如下:

            1)首先,调用free_uid(),用于减少process user的引用计数,linux维持一个per-user的cache,来表明当前用户有多少个打开的file和打开的process,

                 当这个引用计数为0的时候,这个cache将会销毁

            2)运行unhash_process(),把process从pidhash和task list中移除

            3)若process是线程组的最后一个成员,同时线程组的leader为僵尸进程,则会通知该僵尸leader的父进程()。

            4)最后,运行put_task_struct()释放包含着process kernel stack和thread_info的页,以及由slab对收回有slab分配的task_struct结构体


十.僵尸进程

            当一个进程没有parent的时候(比如parent比child先退出),child进程会将current thread group中的一个作为自己的parent,或者如果没有的话,将init

      作为自己的parent。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值