在Linux内核中,通过task_struct这个结构体对进程进行管理,我们可以叫他PCB或者进程描述符。这个结构体定义在include/linux/sched.h中。
鉴于这个结构体的复杂,本文分成多个部分来分析它。
进程状态由结构体中的如下代码定义:
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
其中state的可取的值如下:
/*
* Task state bitmask. NOTE! These bits are also
* encoded in fs/proc/array.c: get_task_state().
*
* We have two separate sets of flags: task->state
* is about runnability, while task->exit_state are
* about the task exiting. Confusing, but this way
* modifying one set can‘t modify the other one by
* mistake.
*/
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_DEAD 16
#define EXIT_ZOMBIE 32
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* in tsk->state again */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256
#define TASK_PARKED 512
#define TASK_STATE_MAX 1024
················
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED (TASK_WAKEKILL | __TASK_TRACED)
其中,有五个互斥状态:
两个终止状态:
以及新增的睡眠状态:
对于TASK_DEAD到TASK_STATE_MAX这五个定义,除了知道TASK_WAKEKILL是和进程睡眠有关以外,别的都没有找到具体含义。希望以后用得上的时候能知道。
sched.h中的以下代码定义进程标识符:
pid_t pid;
pid_t tgid;
Unix系统通过pid来标识进程,linux把不同的pid与系统中每个进程或轻量级线程关联,而unix程序员希望同一组线程具有共同的pid,遵照这个标准linux引入线程组的概念。一个线程组所有线程与领头线程具有相同的pid,存入tgid字段,getpid()返回当前进程的tgid值而不是pid的值。
在Linux系统中,一个线程组中的所有线程使用和该线程组的领头线程(该组中的第一个轻量级进程)相同的PID,并被存放在tgid成员中。只有线程组的领头线程的pid成员才会被设置为与tgid相同的值。注意,getpid()系统调用返回的是当前进程的tgid值而不是pid值。
注意系统中pid的范围是有限的,这也是为什么尽管僵尸进程几乎不占用资源,我们依然要将其回收。因为僵尸进程占用了pid空间,僵尸进程过多会导致没有pid可以分配给新进程。
sched.h中的以下代码定义进程内核栈:
void *stack;
对于每个进程,linux都把两个不同的数据结构紧凑的存放在一个单独为进程分配的内存区域中,一个是内核态的进程堆栈,另一个是线程描述符thread_info。这两个数据结构被定义在一个联合体中,由alloc_thread_info_node分配内存空间。
进程标记反映进程状态的信息,但不是运行状态,用于内核识别进程当前的状态,以备下一步操作。
unsigned int flags; /* per process flags, defined below */
flags可能的取值如下所示:
/*
* Per process flags
*/
#define PF_EXITING 0x00000004 /* getting shut down */
#define PF_EXITPIDONE 0x00000008 /* pi exit done on shut down */
#define PF_VCPU 0x00000010 /* I‘m a virtual CPU */
#define PF_WQ_WORKER 0x00000020 /* I‘m a workqueue worker */
#define PF_FORKNOEXEC 0x00000040 /* forked but didn‘t exec */
#define PF_MCE_PROCESS 0x00000080 /* process policy on mce errors */
#define PF_SUPERPRIV 0x00000100 /* used super-user privileges */
#define PF_DUMPCORE 0x00000200 /* dumped core */
#define PF_SIGNALED 0x00000400 /* killed by a signal */
#define PF_MEMALLOC 0x00000800 /* Allocating memory */
#define PF_NPROC_EXCEEDED 0x00001000 /* set_user noticed that RLIMIT_NPROC was exceeded */
#define PF_USED_MATH 0x00002000 /* if unset the fpu must be initialized before use */
#define PF_USED_ASYNC 0x00004000 /* used async_schedule*(), used by module init */
#define PF_NOFREEZE 0x00008000 /* this thread should not be frozen */
#define PF_FROZEN 0x00010000 /* frozen for system suspend */
#define PF_FSTRANS 0x00020000 /* inside a filesystem transaction */
#define PF_KSWAPD 0x00040000 /* I am kswapd */
#define PF_MEMALLOC_NOIO 0x00080000 /* Allocating memory without IO involved */
#define PF_LESS_THROTTLE 0x00100000 /* Throttle me less: I clean memory */
#define PF_KTHREAD 0x00200000 /* I am a kernel thread */
#define PF_RANDOMIZE 0x00400000 /* randomize virtual address space */
#define PF_SWAPWRITE 0x00800000 /* Allowed to write to swap */
#define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_allowed */
#define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */
#define PF_MUTEX_TESTER 0x20000000 /* Thread belongs to the rt mutex tester */
#define PF_FREEZER_SKIP 0x40000000 /* Freezer should not count it as freezable */
#define PF_SUSPEND_TASK 0x80000000 /* this thread called freeze_processes and should not be frozen */
进程的父子兄弟关系存储在以下代码:
/*
* pointers to (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/
struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
/*
* children/sibling forms the list of my natural children
*/
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent‘s children list */
struct task_struct *group_leader; /* threadgroup leader */
这些字段具体描述如下:
我们知道sys_fork,sys_vfork,和sys_clone最终都是调用的do_fork函数实现,共同目的都是创建一个新进程。
Linux内核中没有独立的“线程”结构,Linux的线程就是轻量级进程,换言之基本控制结构和Linux的进程是一样的(都是通过struct task_struct管理)。
fork是最简单的调用,不需要任何参数,仅仅是在创建一个子进程并为其创建一个独立于父进程的空间。fork使用COW(写时拷贝)机制,并且COW了父进程的栈空间。
vfork是一个过时的应用,vfork也是创建一个子进程,但是子进程共享父进程的空间。在vfork创建子进程之后,父进程阻塞,直到子进程执行了exec()或者exit()。vfork最初是因为fork没有实现COW机制,而很多情况下fork之后会紧接着exec,而exec的执行相当于之前fork复制的空间全部变成了无用功,所以设计了vfork。而现在fork使用了COW机制,唯一的代价仅仅是复制父进程页表的代价,所以vfork不应该出现在新的代码之中。在Linux的manpage中队vfork有这样一段话:It is rather unfortunate that Linux revived this specter from the past. The BSD man page states: "This system call will be eliminated when proper system sharing mechanisms are implemented. Users should not depend on the memory sharing semantics of vfork() as it will, in that case, be made synonymous to fork(2)."
clone是Linux为创建线程设计的(虽然也可以用clone创建进程)。所以可以说clone是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。clone和fork的调用方式也很不相同,clone调用需要传入一个函数,该函数在子进程中执行。此外,clone和fork最大不同在于clone不再复制父进程的栈空间,而是自己创建一个新的。
2018-2019-1 20189218《Linux内核原理与分析》第七周作业
原文:https://www.cnblogs.com/thechosenone95/p/10016163.html