The first assignment: Analysis based on the Linux process model

1. About threads and processes

1. Process 

A process is an application that is running in the system

2. Thread

A thread is the basic unit for the system to allocate processor time resources, or a unit that executes independently within a process

3. The relationship between processes and threads 

·  For the operating systemA process includes at least one thread, which is usually called the main thread.

process starts with the execution of the main thread and then creates one or more additional threads, which is called multitaskingbased on multithreading

thread can create and revoke another thread; multiple threads in the same process can execute concurrently.

Relative to the process ,  the thread is a concept closer to the execution body. It can share data with other threads in the same process, but has its own stack space and an independent execution sequence. 

4. The difference between process and thread

The main difference between processes and threads is that they are different ways of operating system resource management

· From a logical point of view, the meaning of multithreading is that in an application, there are multiple execution parts that can be executed simultaneously. However, the operating system does not regard multiple threads as multiple independent applications to achieve process scheduling and management and resource allocation.

5. How to visualize threads and processes

Take the sandbox as an example. A process is like a sandbox. Threads are like children in a sandbox.

Children are running around in the sandbox and may catch the sand in the eyes of other children, who will kick or bite each other.

However, the difference between these sandboxes is that each sandbox is completely enclosed by walls and ceilings, and no matter how hard the children in the box are to shove the sand, they will not affect the other children in the other sandboxes.

Therefore, each process is like a protected sandbox. No one can enter or exit without permission.

 

2. LINUX open source operating system

1. About LINUX

· Linux is a multi-user, multi-task, multi-thread and multi- CPU operating system based on POSIX and UNIX.

· It can run the main UNIX utility software, applications and network protocols.

· It supports 32-bit and 64-bit hardware .

· It is a multi-user network operating system with stable performance. It is mainly used on computers based on Intel x86 series CPUs.

2. Other

Please refer to the specific file system, file structure, startup process, loader, hard disk partition, desktop environment and usage skills

https://baike.so.com/doc/5349227-5584683.html

 

3. How the LINUX operating system organizes processes

1. Process classification

· System process: can perform management tasks such as memory resource allocation and process switching; moreover, the running of the process is not subject to user intervention, even root users cannot interfere with the running of the system process. User process:
A process that results from the execution of a user program, an application program, or a system program other than the kernel, which can be run or shut down under the control of the user.
For the user process, it can be divided into three categories: interactive process, batch process and daemon process.
· Interactive process : A process started by a shell terminal needs to interact with the user during the execution process. It can run in the foreground or in the background.
· Batch process: This process is a collection of processes responsible for starting other processes in sequence. Daemon process: A
daemon process is a process that runs all the time. It is often started when the Linux system starts and terminated when the system is shut down. They are independent of the controlling terminal and periodically perform some task or wait for some event to occur. For example, the httpd process is always running, waiting for user access. There is also a frequently used crond process, which is similar to a Windows scheduled task and can periodically execute certain tasks set by the user.

2. Process initialization

 

在boot/目录中引导程序把内核从磁盘上加载到内存中,并让系统进入保护模式下运行后,就开始执行系统初始化程序init/main.c。该程序首先确定如何分配使用系统物理内存,然后调用内核各部分的初始化函数分别对内存管理,中断处理,块设备和字符设备,进程管理以及硬盘和软盘硬件进行初始化处理。在完成了这些操作之后,系统各部分已经处于可运行状态。此后程序把自己"手工"移动到任务0(进程0)中运行,并使用fork()调用首次创建出进程1。在进程1种程序将继续进行应用环境的初始化并执行shell登陆程序。而原进程0则会在系统空闲时被调度执行,此时任务0仅执行pause()系统调用,并又会调用调度函数。

 

  "移动到任务0种执行"这个过程由宏move_to_user_mode(include/asm/system.h)完成。它把main.c程序执行流从内核态(特权级0)移动到了用户态(特权级3)的任务0种继续运行。在移动之前,系统在对调度程序的初始化过程(sched_init())中,首先对任务0的运行环境进行的设置。这包括人工预先设置好 任务0数据结构各字段的值(include/linux/shed.h),在全局描述符中添入任务0的任务状态段(TSS)描述符和局部描述符表(LDT)的段描述符,并把它们分别加载到任务寄存器tr和局部描述符表寄存器ldtr中。

 

  这里需要强调的是,内核初始化是一个特殊过程,内核初始化代码也即是任务0的代码。从任务0数据结构中设置的初始化数据可知,任务0的代码段和数据段的基址是0,段限长是640KB。而内核代码段和数据段的基地址时0,段限长是16MB,因此任务0的代码段和数据段分别包含在内核代码段和数据段中。内核初始化程序main.c也即是任务0中的代码,只是在移动到任务0之前系统正以内核态特权级0运行着main.c程序。宏move_to_user_mode的功能就是把运行特权级内核态的0级变换到用户态的3级,但是仍然继续执行原来的代码指令流。

 

  在移动到任务0的过程中,宏move_to_user_mode使用了中断返回指令造成特权级改变的方法。该方法的主要思想是在对站中构筑中断返回指令需要的内容,把返回地址的段选择符设置成任务0代码段选择符,其特权级为3。此后执行中断返回指令iret时将导致系统CPU从特权级0跳转到外层的特权级3上运行。参见下图所示的特权级发生变化时中断返回堆栈结构示意图。

       宏move_to_user_mode首先往内核堆栈中压入任务0数据段选择符和内核堆栈指针。然后压入标志寄存器内容。最后压入任务0代码段选择符合执行中断返回后需要执行的下一条指令的偏移位置。该偏移位置是iret后的一条指令处。

  当执行iret指令时,CPU把返回地址送入CS:EIP中,同时探出对站中标志寄存器内容。由于CPU判断出墓地代码段的特权级是3,与当前内核态的0级不同。于是CPU会把堆栈中的堆栈段选择符合堆栈指针弹出到SS:ESP中。由于特权级发生了变化,段寄存器DS,ES,FS和GS的值变得无效,此时CPU会把这些段寄存器清零。因此在执行了iret指令后需要重新加载这些段寄存器。此后,系统就开始特权级3运行在任务0的代码上。所使用的用户态堆栈还是原来在移动之前使用的堆栈。而其内核态堆栈则被指定为其任务数据解噢股所在页面的顶端开始(PAGE_SIZE+(long)&init_task)。由于以后在创建新进程时,需要复制任务0的任务数据结构,包括其用户堆栈指针,因此需要任务0的用户态堆栈在创建任务1(进程1)之前保持"干净"状态。

2、进程创建的执行

  • fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;
  • Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:
  • 复制一个PCB——task_struct 
1 err = arch_dup_task_struct(tsk, orig);
  • 要给新进程分配一个新的内核堆栈
t= alloc_thread_info_node(tsk, node);
tsk->stack = t;
setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
  • 要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。
  • 从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process
1 *childregs = *current_pt_regs(); //复制内核堆栈
2  childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
3  
4  p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
5  p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址

3、进程实现

在Linux内核的角度并没有线程的概念,都当做进程来实现。线程仅仅被视为一个与其他进程共享某些资源的进程。对Linux来说,线程只是一种进程间共享资源的手段。 
创建线程,线程的创建和进程的创建相同,同样是调用clone()函数,只不过创建线程需要多传递几个参数来表明共享的资源:

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
//参数分别表明父子俩共享地址空间、文件系统资源、文件描述符、信号处理程序,其他与创建进程相同

内核线程:内核线程和普通的进程间的区别在于,内核线程没有独立的地址空间,只能在内核空间运行,不能切换到用户空间。

4、进程终止

一个进程终结时,内核必须释放它占有的所有资源,并通知其父进程。 
进程终止的方式有很多种,进程的析构发生在调用exit()之后。

当进程接收到不能处理也不能忽略的信号或异常时,也可能被动的终结。

不管怎么终结,该任务大部分靠do_exit()完成。

进程相关资源释放后,进程进入EXIT_ZOMBIE状态。

现在占用的资源就是内核栈、thread_info结构、task_struct结构。

现在进程存在的唯一目的就是向它的父进程提供信息。

父进程检索到信息后,或者告知内核那是无关信息后,子进程的task_struct结构才会被释放。 
wait()函数族都是通过系统调用wait4()来实现的。

它的动作就是挂起调用它的进程,直到其中的一个子进程退出,此时函数返回该子进程的PID。最终释放进程描述符时会调用release_task()。

三、进程状态转换

1、进程状态

     · R(TASK_RUNNING),可执行状态。

          只有在该状态的进程才可能在CPU上运行,同一时刻可能有多个进程处于可执行状态。 

  • S(TASK_INTERRUPTIBLE),可中断的睡眠状态。

    处于这个状态的进程因为等待某事件的发生(比如等待socket连接、等待信号量),而被挂起。当这些事件发生时,对应的等待队列中的一个或多个进程将被唤醒。一般情况下,进程列表中的绝大多数进程都处于TASK_INTERRUPTIBLE状态。

  • D(TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。

    与TASK_INTERRUPTIBLE状态类似,进程处于睡眠状态,但是此刻进程是不可中断的。不可中断,指的是进程不响应异步信号,无法用kill命令关闭处于TASK_UNINTERRUPTIBLE状态的进程。

  • T(TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态。

    向进程发送一个SIGSTOP信号,它就会因响应该信号而进入TASK_STOPPED状态(除非该进程本身处于TASK_UNINTERRUPTIBLE状态而不响应信号)。当进程正在被跟踪时,它处于TASK_TRACED状态。

  • Z(TASK_DEAD - EXIT_ZOMBIE),退出状态。

    进程在退出的过程中,处于TASK_DEAD状态,如果它的父进程没有收到SIGCHLD信号,故未调用wait(如wait4、waitid)处理函数等待子进程结束,又没有显式忽略该信号,它就一直保持EXIT_ZOMBIE状态。只要父进程不退出,这个EXIT_ZOMBIE状态的子进程就一直存在。

  • X(TASK_DEAD - EXIT_DEAD),退出状态,进程即将被销毁。

    EXIT_DEAD状态是非常短暂的,几乎不可能通过ps命令捕捉到。

     2、进程状态转换

    盗一张图来说明一下进程状态转换的过程: 

 五、进程调度

1、背景: Linux进程是抢占式的。被抢占的进程仍然处于TASK_RUNNING状态,只是暂时没有被CPU运行。进程的抢占发生在进程处于用户态执行阶段,在内核执行时是不能被抢占的。

             为了能让进程有效地使用系统资源,又能使进程有较快的响应时间,就需要对进程的切换调度采用一定的调度策略。在Linux0.11中采用了基于优先级排队的调度策略。

2、调度相关数据结构

1 task_struct
//这个结构体中表示优先级的有3个元素:static_prio,normal_prio, prio。
//分别表示静态(启动时分配)、普通(静态优先级+调度策略)、动态优先级(可临时提高)。
2 struct sched_class
/*

这个结构体提供了通用调度器(核心调度器)和各个调度方法之间的关联。这个结构体包括的操作:

enqueue_task/dequeue_task,操作就绪队列

sched_yield进程放弃对处理器的控制权

check_preemp_cur 用一个新进程来抢占当前进程

pick_next_task选择下一个将要运行的进程

set_curr_task 调度测率发生变化时

task_tick 每次激活周期性调度器时

new_task 用于fork系统调用和调度器之间的联系。

*/

3 struct rq
 /*内核提供了struct rq数据结构用于表示就绪队列,并为系统所有就绪队列声明了runqueues数组中,该数组每个元素对应一个CPU*/
4 struct sched_entity
/*
调度器可以操作比进程更一般的实体,这个结构体中比较重要的元素包括:

load: 调度实体的权重,它与就绪队列中的负荷比作为调度算法的重要参考依据

run_node: 有了它就可以将调度实体放入红黑树

on_rq:是否接受调度

sum_exec_runtime/prev_sum_exec_runtime:记录消耗的CPU时间(注意是物理时间)

vruntim:记录消耗的虚拟时间
*/

3、调度算法(CFS

定义:CFS是英文Completely Fair Scheduler的缩写,即完全公平调度器,负责进程调度。在Linux Kernel 2.6.23之后采用,它负责将CPU资源,分配给正在执行的进程,目标在于最大化程式互动效能,最小化整体CPU的运用。使用红黑树来实现,算法效率为O(log(n))。

原理:调度算法最核心的两点即为调度哪个进程执行、被调度进程执行的时间多久。前者称为调度策略,后者为执行时间。

执行时间:cfs采用当前系统中全部可调度进程优先级的比重确定每一个进程执行的时间片,即

//分配给进程的时间 = 调度周期 * 进程权重 / 全部进程之和。

算法内核实现:

cfs调度算法使用红黑树来实现,其详细内容可以参考维基百科红黑树的介绍。这里简单讲一下cfs的结构。第一个是调度实体sched_entity,它代表一个调度单位,在组调度关闭的时候可以把他等同为进程。每一个task_struct中都有一个sched_entity,进程的vruntime和权重都保存在这个结构中。 
sched_entity通过红黑树组织在一起,所有的sched_entity以vruntime为key(实际上是以vruntime-min_vruntime为key,是为了防止溢出)插入到红黑树中,同时缓存树的最左侧节点,也就是vruntime最小的节点,这样可以迅速选中vruntime最小的进程。

如图(借鉴网上):

两个重要的结构体:

/*完全公平队列cfs_rq*/

struct cfs_rq {
    struct load_weight load;  //运行队列总的进程权重
    unsigned int nr_running, h_nr_running; //进程的个数

    u64 exec_clock;  //运行的时钟
    u64 min_vruntime; //该cpu运行队列的vruntime推进值, 一般是红黑树中最小的vruntime值

    struct rb_root tasks_timeline; //红黑树的根结点
    struct rb_node *rb_leftmost;  //指向vruntime值最小的结点
    //当前运行进程, 下一个将要调度的进程, 马上要抢占的进程, 
    struct sched_entity *curr, *next, *last, *skip;

    struct rq *rq; //系统中有普通进程的运行队列, 实时进程的运行队列, 这些队列都包含在rq运行队列中  
    ...
    };
/*调度实体sched_entity:记录一个进程的运行状态信息*/

struct sched_entity {
    struct load_weight  load; //进程的权重
    struct rb_node      run_node; //运行队列中的红黑树结点
    struct list_head    group_node; //与组调度有关
    unsigned int        on_rq; //进程现在是否处于TASK_RUNNING状态

    u64         exec_start; //一个调度tick的开始时间
    u64         sum_exec_runtime; //进程从出生开始, 已经运行的实际时间
    u64         vruntime; //虚拟运行时间
    u64         prev_sum_exec_runtime; //本次调度之前, 进程已经运行的实际时间
    struct sched_entity *parent; //组调度中的父进程
    struct cfs_rq       *cfs_rq; //进程此时在哪个运行队列中
};

六、浅谈操作系统进程模型

首先操作系统具有多进程的性质,可以形象地把进程理解为某种类型的一个活动,具有程序,输入,输出以及状态。

单个处理器可以被若干进程共享,它使用某种调度算法决定何时停止一个进程的工作,并转而为另一个·进程服务。

七、参考资料
https://blog.csdn.net/yaosiming2011/article/details/44280797
https://wenda.so.com/q/1403756849501892?src=300
https://blog.csdn.net/lyc_stronger/article/details/51999465

 

 

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325016648&siteId=291194637