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

前言:

         这是一篇关于linux操作系统的简单介绍。linux本身不能算是操作系统,只是一个内核,基于linux内核的操作系统有很多,比如流行的android,ubuntu,红旗linux等等。Linux以它的高效性和灵活性著称。它能够在PC计算机上实现全部的Unix特性,具有多任务、多用户的能力。Linux是在GNU公共许可权限下免费获得的,是一个符合POSIX标准的操作系统。Linux操作系统软件包不仅包括完整的Linux操作系统,而且还包括了文本编辑器、高级语言编译器等应用软件。它还包括带有多个窗口管理器的X-Windows图形用户界面,如同我们使用Windows NT一样,允许我们使用窗口、图标和菜单对系统进行操作。

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

         1.1 进程的概念

                     一个进程是一个程序的一次执行的过程。它和程序不同,程序是静态的,它是一些保存在磁盘上的可执行的代码和数据集合;而进程是一个动态的概念,它是Linux系统的基本的调度单位。一个进程由如下元素组成:

1、 程序的读取上下文,它表示程序读取执行的状态。

2、 程序当前执行目录。

3、 程序服务的文件和目录。

4、 程序的访问权限。

5、 内存和其他分配给进程的系统资源。

     1.2 进程的创建

          fork()提供了创建进程的基本操作,可以说它是Linux系统多任务的基础。 其实fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建。Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:

  • 复制一个PCB——task_struct
err = arch_dup_task_struct(tsk, orig);
  • 给新进程分配一个新的内核堆栈
ti = alloc_thread_info_node(tsk, node);
tsk->stack = ti;
setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
  • 修改复制过来的进程数据,比如pid、进程链表等等
*childregs = *current_pt_regs(); //复制内核堆栈
 childregs->ax = 0; //子进程的fork返回0
p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址

              1.3 进程的组织

                  1.3.1等待队列  

等待队列在内核中有很多用途,尤其用在中断处理,进程同步及定时。等待队列还实现了在时间上的条件等待:希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权。因此,等待队列表示一组睡眠的进程,当某一条件变为真时,由内核唤醒它们。等待队列数据结构为双向链表,其元素包括指向进程描述符的指针。列头是一个类型为wait_queue_head_t的数据结构:

struct _ _wait_queue_head {
      spinlock_t lock;
      struct list_head task_list;
};
typedf struct_ _wait_queue_head wait_queue_head_t;

等待队列是由中断处理程序和主要内核函数修改的,因此必须对其双向连表进行保护以免对其进行同时访问,因为同时访问会导致不可预测的后果。同步是通过等待队列头中的Lock自旋锁打到的。task_list字段是等待进程聊表的头。

 二.进程状态如何转换?

         

◆运行状态(TASK_RUNNING)

指正在被CPU运行或者就绪的状态。这样的进程被成为runnning进程。运行态的进程可以分为3种情况:内核运行态、用户运行态、就绪态。

◆可中断睡眠状态(TASK_INTERRUPTIBLE)

处于等待状态中的进程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。

◆不可中断睡眠状态(TASK_UNINTERRUPTIBLE)

该状态的进程只能用wake_up()函数唤醒。

◆暂停状态(TASK_STOPPED)

当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。

◆僵死状态(TASK_ZOMBIE)

当进程已经终止运行,但是父进程还没有询问其状态的情况。

三.进程是如何调度的?

  •        在 Linux中,进程的运行时间不可能超过分配给他们的时间片,他们采用的是 抢占式多任务处理,所以进程之间的挂起和继续运行无需彼此之间的协作。在一个如 linux这样的多任务系统中,多个程序可能会竞争使用同一个资源,在这种情况下,我们认为,执行短期的突发性工作并暂停运行以等待输入的程序,要比持续占用处理器以进行计算或不断轮询系统以查看是否有输入到达的程序要更好。我们称表现好的程序为 nice程序,而且在某种意义上,这个nice 是可以被计算出来的。 操作系统根据进程的 nice值来决定它的优先级,一个进程的nice值默认为0并将根据这个程序的表现不断变化。长期不间断运行的程序的优先级一般会比较低。进程状态这个是最优先考虑的,也就是说优先级最高的。下面是linux中进程的状态:

TASK_RUNNING:就绪状态,得到CPU就可以运行。

TASK_INTERRUPTIBLE:浅度睡眠,资源到位或者受到信号就会变成就绪态。

TASK_UNINTERRUPTIBLE:深度睡眠,资源到位就会进入就绪态,不响应信号。

TASK_ZOMBIE:僵死态,进程exit后。

TASK_STOPPED:暂停态,收到SIG_CONT信号进入就绪态。

  • 同样的linux也给出了三种的调度策略三种调度策略: SCHED_OTHER、SCHED_FIFO 和 SCHED_RR。

SCHED_OTHER:阻塞和上述相同;被抢占后,其动态优先级会被重新计算,并安排到新的队列尾部;时间片耗尽后,任务不再存在于活动任务队列中,而是被放到过期任务队列中。必须等到过期任务队列切换为活动任务队列,才可能被重新调度。

SCHED_FIFO:如果因为阻塞切换出去,则进入阻塞态,不再继续态列表中,因此不涉及到调度的问题;如果是因为出让CPU或被抢占切换出去,则直接回到队列头中,后面可继续执行。

SCHED_RR:阻塞和被抢占时的情形与SCHED_FIFO相同;如果是因为时间片耗尽造成的切换,则任务回到队列尾部。

  • 并且每个CPU拥有独立的队列,存在专用的负载均衡算法,可实现多CPU之间的进程调配 。

系统的所有就绪队列都在runqueues数组中,数组元素个数与cpu个数相关

DECLARE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);

可执行队列的数据结构为struct runqueue,定义在sched.c中。

struct runqueue {
spinlock_t lock;  /* 保护队列锁 */

unsigned long nr_running;  /* 可运行的任务数 */

unsigned long expired_timestamp;  /* 队列最近被换出时间 */

unsigned long nr_uninterruptible;  /* 处在不可打断睡眠的任务数 */

prio_array_t *active, *expired, arrays[2];  /* 优先级数组相关 */

…

}

运行队列容纳了系统中所有可以运行的进程,它是一个双向循环队列。该队列通过task_struct结构中的两个指针run_list链表来维持。队列的标志有两个:一个是“空进程”idle_task、一个是队列的长度。有两个特殊的进程永远在运行队列中待着:当前进程和空进程。前面我们讨论过,当前进程就是由cureent指针所指向的进程,也就是当前运行着的进程,但是请注意,current指针在调度过程中(调度程序执行时)是没有意义的,为什么这么说呢?调度前,当前进程正在运行,当出现某种调度时机引发了进程调度,先前运行着的进程处于什么状态是不可知的,多数情况下处于等待状态,所以这时候current是没有意义的,直到调度程序选定某个进程投入运行后,current才真正指向了当前运行进程;空进程是个比较特殊的进程,只有系统中没有进程可运行时它才会被执行,Linux将它看作运行队列的头,当调度程序遍历运行队列,是从idle_task开始、至idle_task结束的,在调度程序运行过程中,允许队列中加入新出现的可运行进程,新出现的可运行进程插入到队尾,这样的好处是不会影响到调度程序所要遍历的队列成员,可见,idle_task是运行队列很重要的标志。

另一个重要标志是队列长度,也就是系统中处于可运行状态(TASK_RUNNING)的进程数目,用全局整型变量nr_running表示,在/kernel/fork.c中定义如下:

int nr_running=1;

若 nr_running为0,就表示队列中只有空进程。在这里要说明一下:若nr_running为0,则系统中的当前进程和空进程就是同一个进程。但是Linux会充分利用CPU而尽量避免出现这种情况。

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

        在linux操作系统中,往往是就绪的进程数目很多,在某一时刻必定有进程在就绪态等待处理器使用时间。Linux系统的特性在于可以并发的交互执行多个进程。主要分为非抢占式任务系统和抢占式任务系统。在抢占式任务系统中,调度程序决定了一个进程的运行以便其它进程可以分享到处理器使用时间。抢占是调度程序强制挂起某一个进程的行为。进程从被允许运行到被强占之前可以运行的时间成为时间片。时间片的管理属于进程调度的一部分。有效的管理时间片可以避免个别优先级较高的程序独占处理器。对非抢占的Linux,除非正在运行的进程自己主动停止运行,否则会一直占据处理器使用权。如果该进程出现错误,则整个系统就会崩溃。让步是进程主动放弃处理的行为。同时,进程的优先级可以被设定,而且是动态的。

原文地址:https://www.cnblogs.com/weixia5028/p/8975814.html

时间: 2024-10-09 22:34:41

第一次作业:深入源码分析进程模型的相关文章

第一次作业:深入源码分析进程模型(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,内核通过结构对进程进行

深入源码分析进程模型

1.操作系统是怎么组织进程的 struct task_struct { ...... /* 进程状态 */ volatile long state; /* 指向内核栈 */ void *stack; /* 用于加入进程链表 */ struct list_head tasks; ...... /* 指向该进程的内存区描述符 */ struct mm_struct *mm, *active_mm; ........ /* 进程ID,每个进程(线程)的PID都不同 */ pid_t pid; /* 线

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