深入源码分析进程模型

1.操作系统是怎么组织进程的

struct task_struct {

......

/* 进程状态 */
volatile long state;
/* 指向内核栈 */
void *stack;
/* 用于加入进程链表 */
struct list_head tasks;
......

/* 指向该进程的内存区描述符 */
struct mm_struct *mm, *active_mm;

........

/* 进程ID,每个进程(线程)的PID都不同 */
pid_t pid;
/* 线程组ID,同一个线程组拥有相同的pid,与领头线程(该组中第一个轻量级进程)pid一致,保存在tgid中,线程组领头线程的pid和tgid相同 */
pid_t tgid;
/* 用于连接到PID、TGID、PGRP、SESSION哈希表 */
struct pid_link pids[PIDTYPE_MAX];

........

/* 指向创建其的父进程,如果其父进程不存在,则指向init进程 */
struct task_struct __rcu *real_parent;
/* 指向当前的父进程,通常与real_parent一致 */
struct task_struct __rcu *parent;

/* 子进程链表 */
struct list_head children;
/* 兄弟进程链表 */
struct list_head sibling;
/* 线程组领头线程指针 */
struct task_struct *group_leader;

/* 在进程切换时保存硬件上下文(硬件上下文一共保存在2个地方: thread_struct(保存大部分CPU寄存器值,包括内核态堆栈栈顶地址和IO许可权限位),内核栈(保存eax,ebx,ecx,edx等通用寄存器值)) */
struct thread_struct thread;

/* 当前目录 */
struct fs_struct *fs;

/* 指向文件描述符,该进程所有打开的文件会在这里面的一个指针数组里 */
struct files_struct *files;

........

  /* 信号描述符,用于跟踪共享挂起信号队列,被属于同一线程组的所有进程共享,也就是同一线程组的线程此指针指向同一个信号描述符 */
  struct signal_struct *signal;
  /* 信号处理函数描述符 */
  struct sighand_struct *sighand;

  /* sigset_t是一个位数组,每种信号对应一个位,linux中信号最大数是64
   * blocked: 被阻塞信号掩码
   * real_blocked: 被阻塞信号的临时掩码
   */
  sigset_t blocked, real_blocked;
  sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
  /* 私有挂起信号队列 */
  struct sigpending pending;

........
}

/* wq为某个等待队列的队列头 */
void sleep_on (wait_queue_head_t *wq)
{
    /* 声明一个等待队列结点 */
    wait_queue_t wait;

    /* 用当前进程初始化这个等待队列结点 */
    init_waitqueue_entry (&wait, current);

    /* 设置当前进程状态为TASK_UNINTERRUPTIBLE */
    current->state = TASK_UNINTERRUPTIBLE;

    /* 将这个代表着当前进程的等待队列结点加入到wq这个等待队列 */
    add_wait_queue (wq, &wait);

    /* 请求调度器进行调度,执行完schedule后进程会被移除CPU运行队列,只有等待队列唤醒后才会重新回到CPU运行队列 */
    schedule ();

    /* 这里进程已经被等待队列唤醒,重新移到CPU运行队列,也就是等待的条件已经为真,唤醒后第一件事就是将自己从等待队列wq中移除 */
    remove_wait_queue (wq, &wait);
}

所有处于TASK_RUNNING状态的进程都会被放入CPU的运行队列,它们有可能在不同CPU的运行队列中。

  系统没有为TASK_STOPED、EXIT_ZOMBIE和EXIT_DEAD状态的进程建立专门的链表,因为处于这些状态的进程访问比较简单,可通过PID和通过特定父进程的子进程链表进行访问。

  所有TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE都会被放入相应的等待队列,系统中有很多种等待队列,有些是等待磁盘操作的终止,有些是等待释放系统资源,有些是等待时间经过固定的间隔,每个等待队列它的唤醒条件不同,比如等待队列1是等待系统释放资源A的,等待队列2是等待系统释放资源B的。因此,等待队列表示一组睡眠进程,当某一条件为真时,由内核唤醒这条等待队列上的进程。

等待队列是等待系统释放资源A,而等待队列中所有的进程都是希望能够占有这个资源A的,就像我们编程中用到的信号量,这时候系统的做法不是将这个等待队列中所有的进程都进行唤醒,而是只唤醒一个。内核区分这种互斥进程的原理就是这个等待队列中所有的等待队列结点wait_queue_t中的flags被设置为1(默认是0)

2.进程状态如何转换(给出进程状态转换图)

Linux进程状态有:
R (TASK_RUNNING),可执行状态。
S (TASK_INTERRUPTIBLE),可中断的睡眠状态。
D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。
Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。
T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态。
X (TASK_DEAD - EXIT_DEAD),退出状态,进程即将被销毁。

只有在该状态的进程才可能在CPU上运行。同一时刻可能有多个进程处于可执行状态,这些进程的task_struct结构(进程控制块)
被放入对应CPU的可执行队列中(一个进程最多只能出现在一个CPU的可执行队列中)。进程调度器从各个CPU的可执行队列中分别选择
一个进程在该CPU上运行。
   正在CPU上执行的进程定义为RUNNING状态、可执行但尚未被调度执行的进程定义为READY状态,这两种状态统一为
TASK_RUNNING状态。

只有当进程从“内核运行态”转移到“睡眠状态”时,内核才会进行进程切换操作。在内核态下运行的进程不能被其它进程抢占,而且一个进程不能改变另一个进程的状态。为了避免进程切换时造成内核数据错误,内核在执行临界区代码时会禁止一切中断。

进程的三种基本状态

就绪(Ready)状态

当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。

执行(Running)状态

当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。

阻塞(Blocked)状态

正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。

(1) 就绪→执行处于就绪状态的进程,当进程调度程序为之分配了处理机后,该进程便由就绪状态转变成执行状态。

 (2) 执行→就绪处于执行状态的进程在其执行过程中,因分配给它的一个时间片已用完而不得不让出处理机,于是进程从执行状态转变成就绪状态。

 (3) 执行→阻塞正在执行的进程因等待某种事件发生而无法继续执行时,便从执行状态变成阻塞状态。

 (4) 阻塞→就绪处于阻塞状态的进程,若其等待的事件已经发生,于是进程由阻塞状态转变为就绪状态。

3.进程是如何调度的

static void __sched __schedule(void)
 {
     struct task_struct *prev, *next;
     unsigned long *switch_count;
     struct rq *rq;
     int cpu;

 need_resched:
     /*禁止内核抢占*/
     preempt_disable();
     cpu = smp_processor_id();
     /*获取CPU 的调度队列*/
     rq = cpu_rq(cpu);
     rcu_note_context_switch(cpu);
     /*保存当前任务*/
     prev = rq->curr;

     schedule_debug(prev);

     if (sched_feat(HRTICK))
         hrtick_clear(rq);

     /*
      * Make sure that signal_pending_state()->signal_pending() below
      * can‘t be reordered with __set_current_state(TASK_INTERRUPTIBLE)
      * done by the caller to avoid the race with signal_wake_up().
      */
     smp_mb__before_spinlock();
     raw_spin_lock_irq(&rq->lock);

     switch_count = &prev->nivcsw;
      /*  如果内核态没有被抢占, 并且内核抢占有效
         即是否同时满足以下条件:
         1  该进程处于停止状态
         2  该进程没有在内核态被抢占 */
     if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
         if (unlikely(signal_pending_state(prev->state, prev))) {
             prev->state = TASK_RUNNING;
         } else {
             deactivate_task(rq, prev, DEQUEUE_SLEEP);
             prev->on_rq = 0;

             /*
              * If a worker went to sleep, notify and ask workqueue
              * whether it wants to wake up a task to maintain
              * concurrency.
              */
             if (prev->flags & PF_WQ_WORKER) {
                 struct task_struct *to_wakeup;

                 to_wakeup = wq_worker_sleeping(prev, cpu);
                 if (to_wakeup)
                     try_to_wake_up_local(to_wakeup);
             }
         }
         switch_count = &prev->nvcsw;
     }

     pre_schedule(rq, prev);

     if (unlikely(!rq->nr_running))
         idle_balance(cpu, rq);
     /*告诉调度器prev进程即将被调度出去*/
     put_prev_task(rq, prev);
     /*挑选下一个可运行的进程*/
     next = pick_next_task(rq);
     /*清除pre的TIF_NEED_RESCHED标志*/
     clear_tsk_need_resched(prev);
     rq->skip_clock_update = 0;
    /*如果next和当前进程不一致,就可以调度*/
     if (likely(prev != next)) {
         rq->nr_switches++;
         /*设置当前调度进程为next*/
         rq->curr = next;
         ++*switch_count;
         /*切换进程上下文*/
         context_switch(rq, prev, next); /* unlocks the rq */
         /*
          * The context switch have flipped the stack from under us
          * and restored the local variables which were saved when
          * this task called schedule() in the past. prev == current
          * is still correct, but it can be moved to another cpu/rq.
          */
         cpu = smp_processor_id();
         rq = cpu_rq(cpu);
     } else
         raw_spin_unlock_irq(&rq->lock);

     post_schedule(rq);

     sched_preempt_enable_no_resched();
     if (need_resched())
         goto need_resched;
}

进程提供了两种优先级,一种是普通的进程优先级,第二个是实时优先级,前者使用SCHEED_NORMAL调度策略,后者可选SCHED_FIFO或SCHED_rr调度。任何时候,实时进程的优先级都高于普通进程,实时进程只会被更高级的实时进程抢占,同时实时进程之间是按照FIFO(一次机会做完)或者RR(多次轮转)规则调度的
首先说一下实时进程的调度:
   实时进程,只有静态优先级,因为内核不会根据休眠时间等因素对其静态优先级做调整,默认的实时优先级范围是0~99
不同于普通的进程,系统调度时。实时优先级高的进程总是先于优先级低的进程执行,直到实时优先级高的实时进程无法执行。如果有数个优先级相同的实时进程,那么系统就会按照进程出现在队列上的顺序选择进程。
不同的调度策略的实时进程只有在相同优先级的时候才有可比性:
1)对于FIFO的进程,意味着只有当前进程执行完毕才会轮到其他进程执行。由此可见相当霸道。
2)对于RR进程,一旦时间片消耗完毕,则会将该进程置于队列的末尾,然后运行其他相同优先级的进程,如果没有其他相同优先级的进程,则该进程会继续执行。

对于实时进程,高优先级的进程先执行,他执行到没法执行,采会轮到优先级低的进程执行。等级制度行当森严。
普通进程:
SCHED_ORHER:基于动态优先级进行调度,其动态优先级可以理解为调度器为每个进程根据多个因素计算出的权值。

4.谈谈自己对该操作系统进程模型的看法

Linux系统是一个具有先天病毒免疫能力的操作系统,很少受到病毒攻击。对于一个开放式系统而言,在方便用户的同时,很可能存在安全隐患。

不过,利用Linux自带防火墙、入侵检测和安全认证等工具,及时修补系统的漏洞,就能大大提高Linux系统的安全性,让黑客们无机可乘。

原文地址:https://www.cnblogs.com/zjmu/p/8973737.html

时间: 2024-10-13 11:08:13

深入源码分析进程模型的相关文章

第一次作业:深入源码分析进程模型(Linux kernel 2.6.32)

1.前言 本文基于Linux 2.6.32分析其进程模型,包括进程的概念.组织.转换.调度等内容,帮助对操作系统课程及Linux相关知识的理解和学习. 附Linux Kernel 2.6.32源码下载地址: https://mirrors.edge.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.tar.gz 2.进程的概念 2.1什么是进程? 在正式开始结合源代码分析进程模型之前,我们首先需要搞清楚进程的究竟是什么. 维基百科上对于进程的定义如下:

第一次作业:基于Linux0.01深入源码分析进程模型

一.前言 本文主要基于Linux0.01源代码分析进程模型.Linux 0.01虽然是Linux的第一个发行版本,但是却基本具备了操作系统中最重要的组成部分,同时Linux 0.01只有8500行左右的代码,对于初学者而言学习起来比较简单一点. Linux 0.01源代码下载地址: https://mirrors.edge.kernel.org/pub/linux/kernel/Historic/ 二.进程的定义 进程是程序执行的基本单位.(其中,进程和程序的区别:程序指的是由若干函数组成的可执

第一次作业:基于Linux系统深入源码分析进程模型

1.前言 本文主要基于Linux 2.6源代码分析进程模型.源代码下载地址:https://elixir.bootlin.com/linux/v2.6.39/source 2.进程 定义:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础. 3.Linux系统进程的组织 进程是由进程控制块(PCB).程序段.数据段三部分组成. 3.1 进程控制块 进程控制块(Processing Control Block),是操作系统核心中一种数据结构

第一次作业:深入源码分析进程模型(linux)

一.什么是进程 计算机上有许多可以运行的软件,其中也包括操作系统,这些软件运行时,就产生了一个或多个进程. 二.Linux系统中进程的组织与转换 1>Linux中进程的描述符(即用来描述一个进程的结构体) struct task_struct { ...... volatile long state; // 描述进程的运行状态 void *stack; // 指向内核栈 struct list_head tasks; // 用于加入进程链表 ...... struct mm_struct *mm

第一次作业:Linux2.6源码分析进程模型

1.进程的定义 从系统允许多个程序同时进入CPU那一天开始,我们才有了进程,进程的出现,解决了程序并发执行时对系统资源共享的描述问题,同时顺路解决了程序执行时动态特征的描述问题. 进程:一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是系统进行资源分配和调度运行的基本单位 进程四要素: 1.有一段程序供其执行,该程序不一定是一个进程独享,也可以和其他进程共享. 2.有进程专用的内核空间堆栈. 3.在内核中有一个名为"进程控制块"的task_struct,内核通过结构对进程进行

第一次作业:深入源码分析进程模型

前言:          这是一篇关于linux操作系统的简单介绍.linux本身不能算是操作系统,只是一个内核,基于linux内核的操作系统有很多,比如流行的android,ubuntu,红旗linux等等.Linux以它的高效性和灵活性著称.它能够在PC计算机上实现全部的Unix特性,具有多任务.多用户的能力.Linux是在GNU公共许可权限下免费获得的,是一个符合POSIX标准的操作系统.Linux操作系统软件包不仅包括完整的Linux操作系统,而且还包括了文本编辑器.高级语言编译器等应用

Netty源码分析--内存模型(上)(十一)

前两节我们分别看了FastThreadLocal和ThreadLocal的源码分析,并且在第八节的时候讲到了处理一个客户端的接入请求,一个客户端是接入进来的,是怎么注册到多路复用器上的.那么这一节我们来一起看下客户端接入完成之后,是怎么实现读写操作的?我们自己想一下,应该就是为刚刚读取的数据分配一块缓冲区,然后把channel中的信息写入到缓冲区中,然后传入到各个handler链上,分别进行处理.那Netty是怎么去分配一块缓冲区的呢?这个就涉及到了Netty的内存模型. 当然,我们在第一节的时

Netty源码分析--内存模型(下)(十二)

这一节我们一起看下分配过程 1 PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) { 2 PooledByteBuf<T> buf = newByteBuf(maxCapacity); // 初始化一块容量为 2^31 - 1的ByteBuf 3 allocate(cache, buf, reqCapacity); // reqCapacity = 1024 进入分配

linux源码分析 - 进程

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 最近在回想一些知识点的时候,觉得对进程这一块有些模糊,特别写一篇随笔对进程信息进行巩固和复习. 程序和进程 以我个人的理解就是,程序是一段二进制编码甚至是一个简单的可执行文件,而当程序这段二进制编码放入内存运行时,它就会产生一个或多个进程. CPU时间片 对于CPU来说,它的工作就是不停地执行指令,而由于CPU执行指令的速度非常快,它可以用5ms的时间专门用于执行进程A,5ms的时间专门用于执行进程B,5ms