2. Linux进程管理

本文详细介绍了Linux进程的管理,包括进程的概念、进程描述符、进程状态、进程调度策略及其优先级,以及进程地址空间的分配与管理。重点讨论了进程的生命周期、调度算法如时间片轮转和优先级调度,以及地址空间的虚拟化和物理页的映射机制。
摘要由CSDN通过智能技术生成

2. 进程管理

2.1 Linux进程

  • 进程是程序执行时的一个动态实体,包含程序计数器、全部CPU 寄存器的值和进程堆栈中存储着的一些临时数据,如子程序参数、返回地址及变量等,反映的是当前处理器的活动状态。 而程序是仅包含指令和数据的一段静态代码。
  • Linux 是一个多处理操作系统,进程拥有独立的权限和单一职责,每个进程都运行在各自独立的虚拟地址空间中,只有通过内核控制下的进程通信机制(管道、信号、信号量、消息队列等),它们之间才能发生通信。
  • 从内核的观点看, 进程的目的就是担当分配系统资源(CPU 时间、内存等)的实体。 进程管理的最终目的就是在各进程顺畅执行的条件下,合理分配系统资源给不同的进程。
  • 子进程刚被创建时,是父进程地址空间的一个(逻辑)备份,与父子进程共享程序代码,但它们分别拥有独立的数据备份。因此子进程对堆和栈中的数据进行修改时,对父进程的数据是不会有影响的。

2.2 进程描述符

  • 内核对进程的优先级、进程的状态、地址空间等采用进程描述符表示。在 Linux 内核中,进程用一个相当大的称为 task_struct 的结构表示。下面是从 linux-2.6.29\include\linux\sched.h 中摘抄出来的进程描述的部分信息:

    struct task_struct {
        volatile long state; 	/* 进程状态, -1:不能运行, 0:运行, >0:停止 */
        void *stack;
        atomic_t usage;
        unsigned int flags; 	/* 指示符,进程创建:PF_STARTING,退出:PF_EXITING,在分配内存:PF_MEMALLOC */
        unsigned int ptrace;
        int lock_depth; 		/* BKL lock depth */
        /* 每个进程都会被赋予优先级(称为 static_prio),但实际优先级是基于多因素动态决定的,值越低优先级越高。 */
        int prio, static_prio, normal_prio;
        unsigned int rt_priority;
        const struct sched_class *sched_class;
        struct sched_entity se;
        struct sched_rt_entity rt;
        unsigned char fpu_counter;
        s8 oomkilladj; 			/* OOM kill score adjustment (bit shift). */
        unsigned int policy;
        cpumask_t cpus_allowed;
        struct list_head tasks; /* 提供链接能力,包含prev指针指向前一个任务,next指针指向下一个任务 */
        /* 进程地址空间由mm和active_mm表示,mm代表进程内存描述符,active_mm代表前一进程内存描述符(为改进上下文切换时间的一种优化) */
        struct mm_struct *mm, *active_mm;
        
        /* task state */
        struct linux_binfmt *binfmt;
        int exit_state;
        int exit_code, exit_signal;
        int pdeath_signal; 		/* The signal sent when the parent dies */
        unsigned int personality;
        unsigned did_exec:1;
        pid_t pid;
        pid_t tgid;
        struct task_struct *real_parent; /* real parent process */
        struct task_struct *parent; 	/* recipient of SIGCHLD, wait4() reports */
        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 */
        struct list_head ptraced;
        struct list_head ptrace_entry;
        struct pid_link pids[PIDTYPE_MAX];
        struct list_head thread_group;
        struct completion *vfork_done; 	/* for vfork() */
        int __user *set_child_tid; 		/* CLONE_CHILD_SETTID */
    	int __user *clear_child_tid; 	/* CLONE_CHILD_CLEARTID */
    	...
    };
    

2.3 进程状态

  • 进程描述符中 state 字段描述进程当前的状态。它由一组标志组成,其中每个标志描述一种可能的进程状态。在 2.6 内核中,进程只能处于这些状态中的一种。下面分别对这些状态进行描述。
    • 可运行状态TASK_RUNNING):进程处于运行(系统当前进程)或者准备运行状态(等待系统将 CPU 分配给它)。
    • 等待状态WAITING):进程在等待一个事件或者资源。 Linux 将等待进程分成两类:可中断的等待状态( TASK_TNTERRUPTIBLE)与不可中断的等待状态(TASK_UNINTERRUPTIBLE)。前者可被信号中断,后者直接在硬件条件等待,并且任何情况下都不可中断。
    • 暂停状态TASK_STOPPED) : 进程被暂停, 通常是通过接收一个信号(SIGSTOP、SIGTSTP、 SIGTTIN 或 SIGTTOU)转为暂停状态。正在被调试的进程可能处于停止状态。
    • 僵死状态EXIT_ZOMBIE) : 进程的执行被终止, 但其父进程还没有执行wait4() waitpid()系统调用返回有关该死亡进程的信息。

2.4 进程调度

  • Linux 进程调度指的是在所有可运行状态的进程中选择最值得运行的。每个进程的 task_struct 结构中的 policyprioritycounterrt_priority 这 4 项,是选择进程的依据。
    • policy :进程调度策略,用于区分普通进程和实时进程,实时进程优先于普通进程运行;
    • priority :进程(包括实时和普通)的静态优先级;
    • counter:进程剩余时间片,起始值就是 priority 的值;因为 counter 用于计算一个处于可运行状态的进程值得运行的程度( goodness),所以 counter 也被看做是进程的动态优先级。
    • rt_priority:实时进程特有的优先级别,用于实时进程间的选择。
  • Linux进程分类
    Linux 在执行进程调度的时候,对不同类型的进程采取的策略也不同,一般将 Linux
    分为以下 3 类:
    • 交互式进程:当有用户输入时,这类进程必须很快地激活。通常要求延迟在 50~150 毫秒。典型的交互式进程有控制台命令、文本编辑器、图形应用程序等。
    • 批处理进程(Batch Process):这类进程一般在后台运行,所以不需要非常快地反应,经常被调度期限制。典型的批处理进程有编译器、数据库搜索引擎和科学计算等。
    • 实时进程:这类进程对调度(时间)有非常严格的要求,不能被低优先级进程阻塞,在很短时间内需做出反应。典型的实时进程有音视频应用程序、机器人控制等。
  • Linux进程优先级
    Linux 系统中每一个普通进程都有一个静态优先级,它被调度器作为参考来调度进程。在内核中调度的优先级区间为**[100,139]**,数字越小,优先级越高。一个新的进程总是从它的父进程继承此值。此外, Linux 进程优先级还包括动态优先级、实时优先级等,各个进程优先级描述如下:
    • 静态优先级(priority):被称为“静态”是因为它不随时间而改变,只能由用户进行修改。它指明了在被迫和其他进程竞争 CPU 之前,该进程所被允许的时间片的最大值(20)。
    • 动态优先级(counter): counter 即系统为每个进程运行而分配的时间片。 Linux用它来表示进程的动态优先级。 当进程拥有 CPU 时, counter 就随着时间不断减小,当它递减为 0 时,标记该进程将重新调度。它指明了在当前时间片中所剩余的时间量(最初为 20)。
    • 实时优先级(rt_priority):它的变化范围是从 0~99。任何实时进程的优先级都高于普通的进程。
    • Base time quantum:是由静态优先级决定,当进程耗尽当前 Base time quantum,kernel 会重新分配一个 Base time quantum 给它。静态优先级和 Base time quantum的关系如下所述 :
      • 当静态优先级<120: Base time quantum(ms) = (140 – priority) * 20
      • 当静态优先级>= 120: Base time quantum(ms) = (140 – priority) * 5
  • Linux进程的调度算法
    • 时间片轮转调度算法(round-robin): SCHED_RR 用于实时进程。系统使每个进程依次地按时间片轮流执行的方式。
    • 优先权调度算法: SCHED_NORMAL 用于非实时进程。每次系统都会选择队列中优先级最高的进程运行。 Linux 采用抢占式的优级算法,即系统中当前运行的进程永远是可运行进程中优先权最高的进程。
    • 先进先出调度算法(FIFO): SCHED_FIFO 用于实时进程。采用 FIFO 调度算法选择的实时进程必须是运行时间较短的进程,因为这种进程一旦获得 CPU 就只有等到它运行完或因等待资源主动放弃 CPU 时,其他进程才能获得运行机会。

2.5 进程地址空间

  • Linux 的虚拟地址空间为 0~4GB,其分为内核空间和用户空间两部分。将最高的 1GB(从虚拟地址 0xC0000000~0xFFFFFFFF)留给内核使用,称为“内核空间”;较低的 3GB(从虚拟地址 0x00000000~0xBFFFFFFF)留给用户进程使用,称为“用户空间”。因为每个进程可以通过系统调用进入内核,因此, Linux 内核空间被系统的所有进程共享,实际上对于每个进程来说,它仍然可以拥有 4GB 的虚拟空间。

  • 虚拟地址空间并不是实际的地址空间,在为进程分配地址空间时,根据进程需要的空间进行分配, 4GB 仅仅是最大限额而已,并非一次性将 4GB 分配给进程。一般进程的地址空间总是小于 4GB 的,可以通过查看/proc/pid/maps 文件来获悉某个具体进程的地址空间。

  • 进程的地址空间并不对应实际的物理页, Linux 采用Lazy 的机制来分配实际的物理页(Demand paging 和“写时复制(Copy On Write)的技术”),从而提高实际内存的使用率。虚拟页和物理页
    的对应是通过映射机制来实现的,即通过页表进行映射到实际的物理页。因为每个进程都有自己的页表,因此可以保证不同进程的相同虚拟地址可以映射到不同的物理页,从而为不同的进程都可以同时拥有 4GB 的虚拟地址空间提供了可能。

  • 内核是系统中优先级最高的部分,所以内核函数申请动态内存时系统不会推迟这个请求;但用户进程申请内存空间时,进程的可执行文件被装入后,进程不会立即对所有的代码进行访问。因此内核总是尽量推迟给用户进程分配动态空间。内核分配空间时,通过__get_free_pages()alloc_pages 从分区页框分配器中获得页框; 通过 kmem_cache_alloc()kmalloc()函数使用 slab 分配器为对象分配块;通过 vmalloc() vmalloc32()函数获得一块非连续的内存区。

  • 与进程地址空间有关的全部信息都包含在内存描述符的数据结构 mm_structs中。 进程描述符的 mm字段就是指向这个结构。

  • 进程地址空间得创建与删除

    1. 内核调用copy_mm()函数建立新进程的所有页表和内存描述符,来创建进程的地址空间。

      static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
      {
      	struct mm_struct * mm, *oldmm;
      	int retval;
      
      	tsk->min_flt = tsk->maj_flt = 0;
      	tsk->nvcsw = tsk->nivcsw = 0;
      
      	tsk->mm = NULL;
      	tsk->active_mm = NULL;
      
      	/*如果是内核线程的子线程则直接退出,即 mm 和 active_mm 均为 NULL*/
      	oldmm = current->mm;
      	if (!oldmm)
      		return 0;
      
      	/*内核线程,只是增加当前进程的虚拟空间的引用计数*/
      	if (clone_flags & CLONE_VM) {
      	/*如果共享内存,将 mm 由父进程赋值给子进程,两个进程将会指向同一块内存*/
      		atomic_inc(&oldmm->mm_users);
      		mm = oldmm;
      		goto good_mm;
      	}
      
      	retval = -ENOMEM;
      	mm = dup_mm(tsk); /*完成了对 vm_area_struct 和页面表的复制*/
      	if (!mm)
      		goto fail_nomem;
      good_mm:
      	/* Initializing for Swap token stuff */
      	mm->token_priority = 0;
      	mm->last_interval = 0;
      
      	/*内核线程的 mm 和 active_mm 指向当前进程的 mm_struct 结构*/
      	tsk->mm = mm;
      	tsk->active_mm = mm;
      	return 0;
      
      fail_nomem:
      	return retval;
      }
      
    2. 内核调用 exit_mm()函数释放进程的地址空间

      static void exit_mm(struct task_struct * tsk)
      {
      	m_release(tsk, mm);
      	/*得到读写信号量*/
      	down_read(&mm->mmap_sem);
      	core_state = mm->core_state;
      	if (core_state) {
      		struct core_thread self;
      		/*释放读写信号量*/
      		up_read(&mm->mmap_sem);
      
      		self.task = tsk;
      		self.next = xchg(&core_state->dumper.next, &self);
      
      		if (atomic_dec_and_test(&core_state->nr_threads))
      			complete(&core_state->startup);
      
      		for (;;) {
      			set_task_state(tsk, TASK_UNINTERRUPTIBLE);
      			if (!self.task) /*take 字段可以查看函数 coredump_finish()*/
      				break;
      			schedule();
      		}
      		__set_task_state(tsk, TASK_RUNNING);
      		down_read(&mm->mmap_sem);
      	}
      	atomic_inc(&mm->mm_count);
      	BUG_ON(mm != tsk->active_mm);
      	/* more a memory barrier than a real lock */
      	task_lock(tsk);
      	tsk->mm = NULL;
      	up_read(&mm->mmap_sem);
      	enter_lazy_tlb(mm, current);
      	/*释放用户虚拟空间的数据结构*/
      	clear_freeze_flag(tsk);
      	task_unlock(tsk);
      	mm_update_next_owner(mm);
      	/*递减 mm 的引用计数并是否为 0,如是,则释放 mm 所代表的映射*/
      	mmput(mm);
      }
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值