前置知识

冯诺依曼体系结构

对于现在大多数的计算机底层都遵循着一种结构——冯诺依曼体系结构。它形象地展示了数据在硬件上的流动。

目前,我们所认识的计算机都是由一个个硬件组件组成的。

输入设备:键盘,磁盘,网卡,鼠标等

存储器:我们日常所说的内存

中央处理器:日常所说的CPU,由运算器和控制器等组成

输出设备:磁盘,显示器,打印机等

由图可知,不考虑缓存的情况,CPU 只和内存打交道,CPU只能对内存进行读写,不和外设(输入和输出设备)打交道

换言之:其他设备只和内存进行数据流动

数据在冯诺依曼体系结构中的流动是什么意思?

本质:数据从一个设备 “拷贝” 到另一个设备

引出:体系结构的效率是由拷贝的效率来决定的

为什么软件运行,必须先加载到内存中?

CPU读取,写入数据只能从内存中来进行。

为什么CPU只和内存打交道?

因为冯诺依曼体系结构所确定,深究来讲:因为输入设配和输出设备效率较于CPU慢,会影响CPU的执行效率,所有有了内存,可以使数据流动更加迅速,那为什么不直接采用内存呢?

因为内存贵!就会导致计算机的价格过高,普通百姓买不起。

操作系统

操作系统‌定义:管理软件和硬件的系统软件。它合理组织工作流程,为用户和应用程序提供交互界面(如图形界面或命令行界面),并实现对资源的高效分配与调度。

(广义上)操作系统由两部分组成:内核(进程管理,内存管理,文件管理,驱动管理) + 其他程序(函数库,shell程序等) 

设计OS的目的

对下:与硬件交互,管理所有的软硬件资源

对上:为用户程序提供一个良好的执行环境

1.软硬件体系结构为层状结构

2.访问操作系统,必须使用系统调用 -- 本质就是函数(由系统提供)

3.我们的程序只要访问了硬件,那么它的执行一定贯穿整个软硬件体系结构

4.库可能在底层封装了系统调用

核心功能

在整个计算机软硬件架构中,操作系统的定位是:一款管理软硬件的软件

深度理解如何管理?

对一个对象的管理,需要先记录与它相关的属性,然后再把它组织起来,放到一个数据结构中,对对象的管理,就相当于对数据结构的增删查改,总之:先描述,再组织

系统调用

在开发角度,操作系统对外会表现一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。

库函数

系统调用在使用上,功能比较基础和单一,对用户的要求相对比较高,所以,一些语言的开发者可以对部分系统调用进行二次开发,将其适度封装,从而形成了库函数,有利于上层用户或开发者进行使用,使用成本变低。

思考,操作系统是如何管理进程的呢?

一句话,先描述再组织,先把进程的属性描述起来,再把其组织到一个数据结构中,对进程的管理,就变成了对数据结构的增删查改

进程的概念

课本概念:程序的一个执行实例,正在执行的程序

内核层面:担当分配系统资源(CPU时间,内存)的实体

比较好且对的理解:进程 = 内核数据结构(PCB) + 自己程序的代码和数据

只看概念,肯定理解不了进程,必须要深度去理解和实践。

描述进程 --- PCB

PCB(process control block)概念:进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的结构体。

Linux操作系统下的PCB是:task_struct

task_struct 是LInux内核的一种结构体类型,它会被装载到内存中,并且包含进程的信息,每一个进程都会有唯一一个task_struct

task_struct

内容分类

标示符:描述本进程的唯一标示符,用来区别其他进程,(相当于每一个学生学号, 一一对应)

状态:任务状态,退出代码,退出信号等

优先级:相对于其他进程的优先级

程序计数器:程序中即将被执行的下一条指令的地址

内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

上下文数据:进程执行时处理器的寄存器中的数据

I / O 状态信息:包含显示 I / O 请求,分配给进程的 I / O 设备和被进程使用的文件列表

记账信息:包含处理器时间总和,CPU处理一次进程的时间限制等

......

struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	struct thread_info *thread_info;
	atomic_t usage;
	unsigned long flags;	/* per process flags, defined below */
	unsigned long ptrace;

	int lock_depth;		/* Lock depth */

	int prio, static_prio;
	struct list_head run_list;
	prio_array_t *array;

	unsigned long sleep_avg;
	unsigned long long timestamp, last_ran;
	int activated;

	unsigned long policy;
	cpumask_t cpus_allowed;
	unsigned int time_slice, first_time_slice;

#ifdef CONFIG_SCHEDSTATS
	struct sched_info sched_info;
#endif

	struct list_head tasks;
	/*
	 * ptrace_list/ptrace_children forms the list of my children
	 * that were stolen by a ptracer.
	 */
	struct list_head ptrace_children;
	struct list_head ptrace_list;

	struct mm_struct *mm, *active_mm;

/* task state */
	struct linux_binfmt *binfmt;
	long exit_state;
	int exit_code, exit_signal;
	int pdeath_signal;  /*  The signal sent when the parent dies  */
	/* ??? */
	unsigned long personality;
	unsigned did_exec:1;
	pid_t pid;
	pid_t tgid;
	/* 
	 * pointers to (original) parent process, youngest child, younger sibling,
	 * older sibling, respectively.  (p->father can be replaced with 
	 * p->parent->pid)
	 */
	struct task_struct *real_parent; /* real parent process (when being debugged) */
	struct task_struct *parent;	/* parent process */
	/*
	 * children/sibling forms the list of my children plus the
	 * tasks I'm ptracing.
	 */
	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 */

	/* PID/PID hash table linkage. */
	struct pid pids[PIDTYPE_MAX];

	struct completion *vfork_done;		/* for vfork() */
	int __user *set_child_tid;		/* CLONE_CHILD_SETTID */
	int __user *clear_child_tid;		/* CLONE_CHILD_CLEARTID */

	unsigned long rt_priority;
	unsigned long it_real_value, it_real_incr;
	cputime_t it_virt_value, it_virt_incr;
	cputime_t it_prof_value, it_prof_incr;
	struct timer_list real_timer;
	cputime_t utime, stime;
	unsigned long nvcsw, nivcsw; /* context switch counts */
	struct timespec start_time;
/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
	unsigned long min_flt, maj_flt;
/* process credentials */
	uid_t uid,euid,suid,fsuid;
	gid_t gid,egid,sgid,fsgid;
	struct group_info *group_info;
	kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
	unsigned keep_capabilities:1;
	struct user_struct *user;
#ifdef CONFIG_KEYS
	struct key *session_keyring;	/* keyring inherited over fork */
	struct key *process_keyring;	/* keyring private to this process (CLONE_THREAD) */
	struct key *thread_keyring;	/* keyring private to this thread */
#endif
	int oomkilladj; /* OOM kill score adjustment (bit shift). */
	char comm[TASK_COMM_LEN];
/* file system info */
	int link_count, total_link_count;
/* ipc stuff */
	struct sysv_sem sysvsem;
/* CPU-specific state of this task */
	struct thread_struct thread;
/* filesystem information */
	struct fs_struct *fs;
/* open file information */
	struct files_struct *files;
/* namespace */
	struct namespace *namespace;
/* signal handlers */
	struct signal_struct *signal;
	struct sighand_struct *sighand;

	sigset_t blocked, real_blocked;
	struct sigpending pending;

	unsigned long sas_ss_sp;
	size_t sas_ss_size;
	int (*notifier)(void *priv);
	void *notifier_data;
	sigset_t *notifier_mask;
	
	void *security;
	struct audit_context *audit_context;

/* Thread group tracking */
   	u32 parent_exec_id;
   	u32 self_exec_id;
/* Protection of (de-)allocation: mm, files, fs, tty, keyrings */
	spinlock_t alloc_lock;
/* Protection of proc_dentry: nesting proc_lock, dcache_lock, write_lock_irq(&tasklist_lock); */
	spinlock_t proc_lock;
/* context-switch lock */
	spinlock_t switch_lock;

/* journalling filesystem info */
	void *journal_info;

/* VM state */
	struct reclaim_state *reclaim_state;

	struct dentry *proc_dentry;
	struct backing_dev_info *backing_dev_info;

	struct io_context *io_context;

	unsigned long ptrace_message;
	siginfo_t *last_siginfo; /* For ptrace use.  */
/*
 * current io wait handle: wait queue entry to use for io waits
 * If this thread is processing aio, this points at the waitqueue
 * inside the currently handled kiocb. It may be NULL (i.e. default
 * to a stack based synchronous wait) if its doing sync IO.
 */
	wait_queue_t *io_wait;
/* i/o counters(bytes read/written, #syscalls */
	u64 rchar, wchar, syscr, syscw;
#if defined(CONFIG_BSD_PROCESS_ACCT)
	u64 acct_rss_mem1;	/* accumulated rss usage */
	u64 acct_vm_mem1;	/* accumulated virtual memory usage */
	clock_t acct_stimexpd;	/* clock_t-converted stime since last update */
#endif
#ifdef CONFIG_NUMA
  	struct mempolicy *mempolicy;
	short il_next;
#endif
};

理解上述进程信息,需要不间断地学习操作系统,更深刻地体会操作系统的魅力。

对于内存中的许多进程,OS必定会对每一个进程进行管理 --- 先描述再组织

必备知识:进程 = 内核数据结构 + 自己的代码和数据 

进程 = task_struct + 自己的代码和数据

struct task_struct

{

        代码地址        

        数据地址

        标识符

        优先级

        状态

        ......
};

每一个task_struct 对象存放着对应进程的所有属性

组织进程

所有运行在系统中的进程都以 task_struct 双链表的形式存在内存中

	struct list_head tasks;
struct list_head {
	struct list_head *next, *prev;
};

与我们之前所学的双向链表不同,它的双向链表只有前驱节点和后驱节点,这样的设计可以使进程能被多个双向链表所共有,可以很好地将进程在不同的队列(阻塞队列,运行队列等)进行来回切换,以及被多个队列所共有,后面会有所讲述。

这种设计是如何得到进程对象的地址呢?

很精妙的想法,计算链表指针在该结构体中的偏移量,用该链表地址 - 偏移量就是结构体对象的地址,进行强制类型转化从而得到结构体对象。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐