Linux进程管理

进程

可执行文件交给操作系统就成为进程了。

内核调度的对象是线程,Linux不区分线程和进程,线程只不过是特殊的进程。
进程是执行期的代码和相关资源的总和。
每个进程都有:代码段,数据段,堆,栈。
每个进程都有自己的堆和栈,task_struct包含指向堆和栈的指针,当进行进程切换时,就切换堆栈。
每个进程都共享相同的内核空间。
调用fork()的进程为父进程,新产生的进程称为子进程,在该调用结束时,再返回到这个相同位置,父进程恢复执行,子进程开始执行。
创建好进程后,使用exec()就可以创建新的地址空间,并把新的代码程序载入执行。

进程描述符及任务结构:内核线程,进程,线程都使用相同的PCB结构

进程描述符(PCB)

PCB就是task_struct,其中包含进程标识信息,处理器线程信息,进程调度信息,进程控制信息。

Linux进程管理_第1张图片

该结构定义在文件中。
在32位,task_struct大约有1.7KB,在64位比这个大一些。
包含:打开的文件,挂起的信号,进程的地址空间(页),进程的栈指针,进程的堆地址等。

struct task_struct {
volatile long state;  //说明了该进程是否可以执行,还是可中断等信息
unsigned long flags;  //Flage 是进程号,在调用fork()时给出
int sigpending;    //进程上是否有待处理的信号
mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
                        //0-0xBFFFFFFF for user-thead
                        //0-0xFFFFFFFF for kernel-thread
//调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
volatile long need_resched;
int lock_depth;  //锁深度
long nice;       //进程的基本时间片
//进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER
unsigned long policy;
struct mm_struct *mm; //进程内存管理信息
int processor;
//若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新
unsigned long cpus_runnable, cpus_allowed;
struct list_head run_list; //指向运行队列的指针
unsigned long sleep_time;  //进程的睡眠时间
//用于将系统中所有的进程连成一个双向循环链表, 其根是init_task
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct list_head local_pages;       //指向本地页面      
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt;  //进程所运行的可执行文件的格式
int exit_code, exit_signal;
int pdeath_signal;     //父进程终止时向子进程发送的信号
unsigned long personality;
//Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
int did_exec:1; 
pid_t pid;    //进程标识符,用来代表一个进程
pid_t pgrp;   //进程组标识,表示进程所属的进程组
pid_t tty_old_pgrp;  //进程控制终端所在的组标识
pid_t session;  //进程的会话标识
pid_t tgid;
int leader;     //表示进程是否为会话主管
struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
struct list_head thread_group;   //线程链表
struct task_struct *pidhash_next; //用于将进程链入HASH表
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit;  //供wait4()使用
struct completion *vfork_done;  //供vfork() 使用
unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值

//it_real_value,it_real_incr用于REAL定时器,单位为jiffies, 系统根据it_real_value
//设置定时器的第一个终止时间. 在定时器到期时,向进程发送SIGALRM信号,同时根据
//it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
//当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送
//信号SIGPROF,并根据it_prof_incr重置时间.
//it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
//状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据
//it_virt_incr重置初值。
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_value;
struct timer_list real_timer;   //指向实时定时器的指针
struct tms times;      //记录进程消耗的时间
unsigned long start_time;  //进程创建的时间
//记录进程在每个CPU上所消耗的用户态时间和核心态时间
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; 
//内存缺页和交换信息:
//min_flt, maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换
//设备读入的页面数); nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
//cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
//在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1; //表示进程的虚拟地址空间是否允许换出
//进程认证信息
//uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid
//euid,egid为有效uid,gid
//fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件
//系统的访问权限时使用他们。
//suid,sgid为备份uid,gid
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; //记录进程在多少个用户组中
gid_t groups[NGROUPS]; //记录进程所在的组
//进程的权能,分别是有效位集合,继承位集合,允许位集合
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
int keep_capabilities:1;
struct user_struct *user;
struct rlimit rlim[RLIM_NLIMITS];  //与进程相关的资源限制信息
unsigned short used_math;   //是否使用FPU
char comm[16];   //进程正在运行的可执行文件名
 //文件系统信息
int link_count, total_link_count;
//NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空
struct tty_struct *tty;
unsigned int locks;
//进程间通信信息
struct sem_undo *semundo;  //进程在信号灯上的所有undo操作
struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
//进程的CPU状态,切换时,要保存到停止进程的task_struct中
struct thread_struct thread;
  //文件系统信息
struct fs_struct *fs;
  //打开文件信息
struct files_struct *files;
  //信号处理函数
spinlock_t sigmask_lock;
struct signal_struct *sig; //信号处理函数
sigset_t blocked;  //进程当前要阻塞的信号,每个信号对应一位
struct sigpending pending;  //进程上是否有待处理的信号
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
u32 parent_exec_id;
u32 self_exec_id;

spinlock_t alloc_lock;
void *journal_info;
};

进程在用户空间有自己的堆和栈,并且在内核空间还有内核给分配的栈,用于执行系统调用、中断处理、异常处理等在内核模式下的操作。
当进程从用户空间切换到内核空间执行系统调用时,会使用内核栈来保存进程的状态、寄存器值等信息。系统调用执行完成后,进程将从内核空间返回用户空间,此时可能会切换回原来的用户栈。
在进程的内核栈的栈底,存在着thread_info的数据结构,其中有指向task_struct的指针。
在内核栈中,我们想要获得进程信息,那么就使用current宏,current宏通过硬件获得内核栈底的存放的指向task_struct的指针。

PID如果不修改,就是short int的最大值,为32768,就是操作系统允许运行的最大进程数量

任务队列

内核将进程都存放在叫做任务队列(task list)的双向循环链表中。
在内核中,大部分处理进程的代码都是直接通过task_struct进行的,因此通过current宏来找到正在运行进程的进程描述符的速度就尤为重要。

进程的状态

在进程描述符中,有进程的状态state。

  • TASK_RUNNING:正在运行或者在运行队列
  • TASK_INTERRUPTIBLE:被阻塞等待唤醒阻塞返回或者信号中断
  • TASK_UNINTERRUPTIBLE:只能阻塞唤醒
  • _TASK_TRACED:被其他进程跟踪
  • _TASK_STOPPED

进程家族树

所有的进程都是PID为1的init进程的后代。
task_struct中包含父进程的指针。

进程创建:在创建时候就定好了是用户进程还是内核进程


一些操作系统(非Unix)的产生进程的机制:
在新的地址空间创建进程空间,读入可执行文件,最后开始执行。
Unix操作系统进程创建比较特别,它将上述的步骤分解为两个步骤执行:fork()和exec()。

fork()通过拷贝当前进程创建一个子进程,exec()负责读取可执行文件并且将其载入地址空间开始运行。

fork的唯一开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。

写时复制:页为只读状态

fork():底层调用clone(SIGCHLD,0)

过程:

  1. 为新进程创建一个内核栈,栈低创建thread_info结构(包含task_struct,页表等和父进程完全相同),进程状态为TASK_UNINTERRUPTIABLE。
  2. 修改创建的task_struct内容。
  3. 返回子进程指针。
  4. 新创建的子进程被唤醒并且投入运行。

线程在Linux中的实现:linux调度的是进程,没有线程

线程机制提供了在同一程序内共享内存地址空间运行的一组线程,这些线程还可以共享打开的文件和其他资源。

线程的实体是函数,进程的实体是程序

从内核角度来讲,linux没有线程。线程仅仅被视为一个与其他进程共享资源的进程(地址空间,文件等),每个线程都有自己的task_struct,所以看起来就像普通进程一样。

创建线程:clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND , 0)

上述和clone(SIGCHLD,0)差不多,只是父子共享地址空间,文件系统资源,文件描述符和信号处理程序。
传递给clone的参数标志决定了子进程和父进程之间共享的东西,这就是linux中的线程。

内核线程

内核线程没有独立的地址空间(task_struct的mm指针被设置为NULL)。
只在内核空间运行,从不切换到用户空间。
内核线程只能由其他内核线程创建。
从内核线程中创建新的内核线程的方法:

struct task_struct kthread_create(int (threadfn)(void data),void data,);

进程终结

工作内容:

  1. 删除定时器。
  2. 释放进程占用的mm_struct(如果没有进程使用mm_struct,就释放)
  3. 向父进程发送信号,给子进程重新找养父
  4. 递减文件描述符,文件系统数据的引用计数。

Linux线程模型

Linux是一对一的线程模型。

在一对一线程模型中:
每个用户线程都有一个对应的内核线程。
用户线程执行系统调用时,切换到与之对应的内核线程执行,以执行系统调用中的内核功能。

内核线程由操作系统来管理和分配。
执行流程:

  1. 用户线程执行系统调用: 用户线程在用户态执行,当需要进行系统调用时,通过系统调用指令触发切换到内核态。

  2. 内核线程执行: 一旦进入内核态,控制被交给了内核线程,它负责执行系统调用中涉及的内核功能。

  3. 系统调用完成: 内核线程执行完系统调用中的内核功能后,将控制权返回给用户线程。

  4. 用户线程继续执行: 用户线程回到用户态,继续执行用户程序的下一条指令。

你可能感兴趣的:(Linux,linux,windows,运维)