openEuler 笔记:进程1:程序的加载运行、进程的描述( PCB、进程状态 )

1,408 阅读6分钟

本系列学习笔记基本上是博主的《 openEuler 操作系统》读书笔记,中间插入一些自己查的资料以及翻到的感觉有用的源代码

默认架构为 ARM

程序及其加载执行

类 UNIX 的二进制程序一般为 ELF 格式,一个【逻辑意义上作为整体的程序】会被按照内容类型划分为数个 Segment 进行存储。主要的 Segment 有 :.text ,存机器指令序列;.data 存可变的全局变量及静态局部变量;.rodata 存只读数据、常量;.bss 存未初始化的全局变量。

加载,是操作系统将 ELF 读入内存的过程。首先检查 ELF 头,确定是否可以运行。然后读端头表,得到各个段的信息,为需要装入内存的段分配空间,然后把这些段加载到内存中。

执行。程序入口地址存在 ELF 头中,OS 读到该地址后到 .text 找到入口,将入口地址赋给 PC ,程序获得 CPU 控制权。

运行过程中,PC 保存即将执行的指令地址,LR(链接寄存器)保存函数调用返回后的下一条指令地址。

每个运行中的函数都拥有一个栈帧,其为函数的每次调用构建独立的上下文。当函数调用新的函数时,新的函数会被分配新的栈帧。函数运行结束后栈帧会被弹出,系统从下面的栈帧(即发起对刚刚结束的函数调用的函数的栈帧)中取出之前保存的 FP、LR 值,继续运行

进程的描述

操作系统使用 PCB (Process Control Block,进程控制块)对进程进行描述,操作系统通过 PCB 感知进程。

PCB

PCB 定义在 include/linux/sched.hstruct task_struct,是一个六百二十多行的结构体。

启动一个程序时,操作系统先创建 PCB ,然后根据其中的信息对进程进行管理和控制,程序运行后系统释放 PCB 。其中的主要信息包含描述信息、控制信息、 CPU 上下文、资源管理信息

描述信息

  • 进程标识符,OS 用它来标记每个进程;

  • 用户标识号,区分某个进程属于哪个用户

        kuid_t			loginuid;
        unsigned int		sessionid;
    
  • 家族关系,表明该进程与其父进程、祖先进程、子进程、兄弟进程等的关系

        /*
         * Pointers to the (original) parent process, youngest child, younger sibling,
         * older sibling, respectively.  (p->father can be replaced with
         * p->real_parent->pid)
         */
    
        /* Real parent process: */
        struct task_struct __rcu	*real_parent;
    
        /* Recipient of SIGCHLD, wait4() reports: */
        struct task_struct __rcu	*parent;
    
        /*
         * Children/sibling form the list of natural children:
         */
        struct list_head		children;
        struct list_head		sibling;
        struct task_struct		*group_leader;
    

    real_parent 是创建该进程的进程,而 parent 是响应信号相关的父进程——比如 SIGCHLD 就会被发送给 parent。这么设计是因为:有些情况下真父进程可能先终止,这样如 init 这样的其他进程就会成为新的父进程,但不会改变真父进程的值。

控制信息

  • 进程状态(就绪、阻塞、运行、终止)

        volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    
        /* 相关宏定义如下 */
    
        /* Used in tsk->state: */
        #define TASK_RUNNING		0x0000
        #define TASK_INTERRUPTIBLE		0x0001
        #define TASK_UNINTERRUPTIBLE	0x0002
        #define __TASK_STOPPED		0x0004
        #define __TASK_TRACED		0x0008
        #define TASK_PARKED			0x0040
        #define TASK_DEAD			0x0080
        #define TASK_WAKEKILL		0x0100
        #define TASK_WAKING			0x0200
        #define TASK_NOLOAD			0x0400
        #define TASK_NEW			0x0800
        #define TASK_STATE_MAX		0x1000
    
  • 进程优先级

        int				prio;
        int				static_prio;
        int				normal_prio;
        unsigned int		rt_priority;
    
    • 静态优先级:进程启动时指定,值越小越高。一般不用,但可用系统调用 nice() 修改;
    • 动态优先级:可以因调度策略的改变而临时变动;
    • 实时:有限程度仅与实时优先级有关,值越小越高。实时进程调度总优于普通进程
  • 记账信息:记录进程占有、利用资源的情况,调度以这些信息为依据进行(如剥夺等),如时间方面、上下文切换方面的:

            u64				utime;
            u64				stime;
        #ifdef CONFIG_ARCH_HAS_SCALED_CPUTIME
            u64				utimescaled;
            u64				stimescaled;
        #endif
            u64				gtime;
            struct prev_cputime		prev_cputime;
        #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
            struct vtime			vtime;
        #endif
    
        #ifdef CONFIG_NO_HZ_FULL
            atomic_t			tick_dep_mask;
        #endif
            /* Context switch counts: */
            unsigned long			nvcsw;
            unsigned long			nivcsw;
    
            /* Monotonic time in nsecs: */
            u64				start_time;
    
            /* Boot based time in nsecs: */
            u64				real_start_time;
    

CPU 上下文

CPU 上下文指某时刻 CPU 各寄存器中的值,用于进程切换时保存状态。其保存于 task_struct->thread_struct->cpu_context

从下面的代码中可以看到,cpu_context 就是各个寄存器中的值的集合

/* arch/arm64/include/asm/processor.h */
struct cpu_context {
	unsigned long x19;
	unsigned long x20;
	unsigned long x21;
	unsigned long x22;
	unsigned long x23;
	unsigned long x24;
	unsigned long x25;
	unsigned long x26;
	unsigned long x27;
	unsigned long x28;
	unsigned long fp;
	unsigned long sp;
	unsigned long pc;
};

struct thread_struct {
	struct cpu_context	cpu_context;	/* cpu context */
	/* 省略其他变量,thread_struct 结构体中包含所有与 CPU 相关的状态信息 */
};

资源管理信息

这部分信息在 PCB 中占比最大,包含存储器、文件系统、使用 IO 设备的信息等,主要与内存和文件相关。

    void *stack;	// 指向进程内核栈,内核栈是进程在内核态用的栈,在内核空间

	// 进程的用户空间描述符
    struct mm_struct		*mm;
    struct mm_struct		*active_mm;

	/* Filesystem information: */
	struct fs_struct		*fs;	// 与进程相关的文件系统

	/* Open file information: */
	struct files_struct		*files;	// 进程正打开的文件列表

	// 内存描述符,定义在 include/linux/mm_types.h
	struct mm_struct {
		spinlock_t arg_lock; /* protect the below fields,自旋锁 */
        // 描述内存中各个段的起始位置,包括栈、映射段、堆、BSS段、数据段、代码段
		unsigned long start_code, end_code, start_data, end_data;
		unsigned long start_brk, brk, start_stack;
		unsigned long arg_start, arg_end, env_start, env_end;
        //......
    };

	// 进程关联的文件系统信息,include/linux/fs_struct.h
    struct fs_struct {
        int users;				// 该结构的引用用户数
        spinlock_t lock;
        seqcount_t seq;
        int umask;
        int in_exec;
        struct path root, pwd;	// 根与当前目录
    } __randomize_layout;

	/* include/linux/fdtable.h
     * Open file table structure
     */
    struct files_struct {
      /*
       * read mostly part
       */
        atomic_t count;			    // 引用计数
        struct fdtable __rcu *fdt;	// 默认指向 fdt,可用于动态申请内存
        struct fdtable fdtab;		// 为 fdt 提供初始值
        // fdt、fdtable 是管理文件描述符用的
        // ...
    };

	// path. include/linux/path
    struct path {
        struct vfsmount *mnt;
        struct dentry *dentry;
    } __randomize_layout;

	struct fdtable {
        unsigned int max_fds;
        struct file __rcu **fd;      /* current fd array */
        unsigned long *close_on_exec;
        unsigned long *open_fds;
        unsigned long *full_fds_bits;
        struct rcu_head rcu;
    };

进程的状态

进程当前状态由 PCB 中的状态值描述。这里的状态就是运行、就绪、阻塞、终止(僵尸、死亡)

    /* Used in tsk->state: */
    #define TASK_RUNNING		0x0000
    #define TASK_INTERRUPTIBLE		0x0001
    #define TASK_UNINTERRUPTIBLE	0x0002
    #define __TASK_STOPPED		0x0004
    #define __TASK_TRACED		0x0008
    /* Used in tsk->exit_state: */
    #define EXIT_DEAD			0x0010
    #define EXIT_ZOMBIE			0x0020
    #define EXIT_TRACE			(EXIT_ZOMBIE | EXIT_DEAD)
    /* Used in tsk->state again: */
    #define TASK_PARKED			0x0040
    #define TASK_DEAD			0x0080
    #define TASK_WAKEKILL		0x0100
    #define TASK_WAKING			0x0200
    #define TASK_NOLOAD			0x0400
    #define TASK_NEW			0x0800
    #define TASK_STATE_MAX		0x1000
  • 就绪:进程位于运行队列中,已获得 CPU 外的所有资源,等待 OS 选中占用 CPU
  • 运行:当前进程主动放弃或被抢占,则转为就绪;当前进程等待资源或事件时,转为阻塞;运行结束,进入终止状态(僵尸/等待)
  • 阻塞:通常是等待外部事件(如 I/O ),需要等来了才就绪。但可被系统调用、信号等唤醒(阻塞分轻度中度深度,不同的程度需要的条件不同)
  • 终止:
    • 僵尸:父进程未回收进程及其占用的资源(包括 PCB )。若父进程先结束,init 会成为子进程的 parent ,待结束后回收资源
    • 死亡:结束后由父进程回收资源