进程

进程是处于执行期的程序以及相关资源的总称,是正在执行的代码的实时结果.

线程

执行线程简称线程,是进程中的活动对象,每个线程都具有独立的程序计数器,进程栈,和一组进程寄存器.

虚拟处理器与虚拟内存

进程提供两种机制,虚拟处理器虚拟内存,实际可能是许多进程分享同一个处理器,虚拟处理器会让进程觉得自己在独享处理器,虚拟内存同样,会让进程觉得自己拥有整个系统的内存资源.

创建进程

使用fork函数创建进程

...
NAME
   fork - create a child process

SYNOPSIS
   #include <sys/types.h>
   #include <unistd.h>

   pid_t fork(void);

DESCRIPTION
   fork()  creates  a new process by duplicating the calling process.  The new process is
   referred to as the child process.  The calling process is referred to  as  the  parent
   process.

   The  child  process and the parent process run in separate memory spaces.  At the time
   of fork() both memory spaces have the same  content.   Memory  writes,  file  mappings
   (mmap(2)),  and unmappings (munmap(2)) performed by one of the processes do not affect
   the other.
   ...
   RETURN VALUE
   On  success, the PID of the child process is returned in the parent, and 0 is returned
   in the child.  On failure, -1 is returned in the parent, no child process is  created,
   and errno is set appropriately
   ...

fork函数
Creat thread
child process may use all of father's resources
pid_t fork(void);
– 参数:无
– 返回值:执行成功,返回子进程pid给父进程,0返回给子进程;出现错误,返回-1给父进程。执行失败的唯一情况是内存不够或者id号用尽,不过这种情况几乎很少发生。
– 系统函数fork 调用成功,会创建一个新的进程,它几乎会调用差不多完全一样的fork 进程
– 子进程的pid 和父进程不一样,是新分配的
– 子进程的ppid(他爷爷的ID) 会设置为父进程的pid,也就是说子进程和父进程各自的
“父进程”不一样.
– 子进程中的资源统计信息会清零.
– 挂起的信号会被清除,也不会被继承.
– 所有文件锁也不会被子进程继承.  
父进程调用子进程,然后自己向下执行,同时子进程被调用后也开始执行.
fork系统调用从内核返回两次,一次返回到父进程,另一次返回到新产生的子进程.
clone()的所有参数定义在 linux/sched.h 中.

调用关系

创建进程 -> fork() -> clone() -> do_fork() -> copy_process()

copy_process()函数完成

回到do_fork函数,如果copy_process函数返回成功,那么内核会有意让子进程先执行,一般子进程会马上调用exec这样会避免写时拷贝的开销.

进程描述符

/*进程描述符task_struct结构*/
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
    /*
     * For reasons of header soup (see current_thread_info()), this
     * must be the first element of task_struct.
     */
    struct thread_info      thread_info;
#endif
    /* -1 unrunnable, 0 runnable, >0 stopped: */
    volatile long           state;

    /*
     * This begins the randomizable portion of task_struct. Only
     * scheduling-critical items should be added above here.
     */
    randomized_struct_fields_start

    void                *stack;
    atomic_t            usage;
    /* Per task flags (PF_*), defined further below: */
    unsigned int            flags;
    unsigned int            ptrace;
    /*
     * WARNING: on x86, 'thread_struct' contains a variable-sized
     * structure.  It *MUST* be at the end of 'task_struct'.
     *
     * Do not put anything below here!
     */
};

分配进程描述符

Linux通过slab分配task_struct结构,这样能够达到对象复用缓存着色(cache coloring).使用slab动态生成task_struct结构,只需在栈底或栈顶创建一个新的结构struct thread_info.

current获得当前正在运行程序的进程描述符

在内核中访问任务通常需要获得指向他的task_struct的指针,内核中绝大多数处理进程的代码都是通过task_struct进行的,可以通过current宏查找当前正在运行的进程的进程描述符.

thread_info结构体

.\linux源码目录\arch\arm\include\asm\thread_info.h
/*
 * low level task data that entry.S needs immediate access to.
 * __switch_to() assumes cpu_context follows immediately after cpu_domain.
 */
struct thread_info {
    unsigned long       flags;      /* low level flags */
    int         preempt_count;  /* 0 => preemptable, <0 => bug */
    mm_segment_t        addr_limit; /* address limit */

    struct task_struct  *task;      /* main task structure */
    __u32           cpu;        /* cpu */
    __u32           cpu_domain; /* cpu domain */
    struct cpu_context_save cpu_context;    /* cpu context */
    __u32           syscall;    /* syscall number */
    __u8            used_cp[16];    /* thread used copro */
    unsigned long       tp_value[2];    /* TLS registers */
#ifdef CONFIG_CRUNCH
    struct crunch_state crunchstate;
#endif
    union fp_state      fpstate __attribute__((aligned(8)));
    union vfp_state     vfpstate;
#ifdef CONFIG_ARM_THUMBEE
    unsigned long       thumbee_state;  /* ThumbEE Handler Base register */
#endif
};

PID的存放

内核通过一个唯一的进程标识值(process identification value)即PID来表示每个进程。PID是一个数,表示为pid_t隐含类型,实际上就是一个int类型。为了与老版本的Unix和Linux兼容,PID的最大值默认设置为32768(short int短整型的最大值),这个限值定义在<linux/threads>中。内核把每个进程的PID存放在它们各自的进程描述符中.

state域

进程描述符中的state域描述了进程的当前状态,系统中的每个进程都必然处于五种进程状态中的一种:

设置当前的进程状态

调整进程状态的函数

set_task_state(task,state)/*将任务task的状态设置为state*/  

该函数将指定的进程设置为指定的状态,在SMP系统中,会设置内存屏障来强制其他处理器作重新排序 ,否则等价于

task->state = state;/*设置状态位的状态为state*/

set_current_state(state)和set_task_state(current,state)含义相同,具体在头文件 <linux/sched.h>中.

fork函数与vfork函数的区别

页表项

内核线程

内核线程是独立运行在内核空间的标准进程,与普通进程的区别是它没有独立的地址空间,所有的内核线程都运行在同一个地址空间范围内.

查看内核线程命令:

ps -ef 

创建内核线程

#define kthread_run(threadfn, data, namefmt, ...)              \
({                                     \
    struct task_struct *__k                        \
        = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
    if (!IS_ERR(__k))                          \
        wake_up_process(__k);                      \
    __k;                                   \
})

终结内核线程

do_eixt完成的工作

  1. 将task_struct中的标志成员设置为PF_EXITING.(设置进程描述符中的flags为关闭标志)
  2. 调用del_timer_sync()删除任一内核定时器,确保没有定时器在排队,也没有定时器处理程序在运行.
  3. 如果BSD的进程记账功能是开启的,do_exit()调用acct_update_integrals(),输出记账信息
  4. 调用exit_mm()函数释放进程占用的mm_struct,如果没有别的进程使用他们(说明内存没有被共享),就彻底释放他们.
  5. 调用sem__exit()函数,如果进程在排队等候IPC信号,那么就离开队列
  6. 调用exit_files()和exit_fs(),以分别递减文件描述符和文件系统的引用计数,如果引用计数降为0,说明该进程没有使用相应资源,可以释放.
  7. 将存放在task_struct中的exit_code成员中的任务退出代码置为由exit()提供的退出代码,或者去完成任何其他内核机制规定的退出动作,退出代码存放在这里供父进程随时检索.
  8. 调用exit_notify通知父进程,给子进程找养父,(父进程要终结,那么他必须为他下面的子进程,找到养父,否则他下面的子进程就都成为了孤儿进程),养父可以是老祖宗Init也可以是其他进程.并将进程状态task_struct结构中的exit_state设置为EXIT_ZOMBLE.
  9. do_exit调用schedule切换到新的进程(调用调度程序,放弃处理器时间,让调度程序去调度其他进程),因为处于EXIT_ZOMBIE状态的进程不会再被调度,所以这是进程所执行的最后一段代码,do_exit永不返回.

    /*
    * Per process flags
    */
    #define PF_IDLE         0x00000002  /* I am an IDLE thread */
    #define PF_EXITING      0x00000004  /* Getting shut down */
    /*...*/

如果进程而资源没有共享,那么所有的相关资源都被释放掉了,并处于EXIT_ZOMBIE退出状态,它占用的所有内存就是内核栈,thread_info结构和task_struct结构,
此时该进程存在的唯一目的,是为了向父进程提供信息,父进程检索到这些信息后,确认他已经死亡,为它做善后处理,该进程所持有的剩余内存全部归还给系统使用.

处理掉僵死进程

---释放进程描述符

父进程通过wiat4系统调用负责实现子进程的善后处理,他的标准动作:

孤儿进程,进退维谷

do_exit中会调用exit_notify,该函数会调用forget_original_parent,而后者回调用find_new_reaper来执行寻父过程,这段CODE会在当前线程组内给他找养父,如果不行就找Init.


小结:

进程间的关系

进程的存放和表示

current宏

进程的创建

写时拷贝

线程

wait系统调用

父进程收集后代信息的手段

进程如何消亡

程序通过显示或隐式调用exit系统调用退出执行,这个函数会终结进程,并将其占用的资源释放掉.此时该线程处于僵死状态,并且不能够运行. 需要使用wait函数族来对该进程实现销毁处理 .


01-05 15:19
查看更多