进程的概念
本文介绍了计算机系统的核心架构和操作系统原理。首先阐述了冯诺依曼体系结构的基本组成(输入设备、存储器、CPU、输出设备)及其数据流动机制,指出CPU仅与内存交互以提高效率。其次详细说明了操作系统的定义、功能和层次结构,强调其作为软硬件管理者的核心地位。重点分析了进程管理机制,通过"先描述再组织"的方式,以PCB(task_struct)数据结构记录进程属性,并以双向链表形式组织
前置知识
冯诺依曼体系结构
对于现在大多数的计算机底层都遵循着一种结构——冯诺依曼体系结构。它形象地展示了数据在硬件上的流动。

目前,我们所认识的计算机都是由一个个硬件组件组成的。
输入设备:键盘,磁盘,网卡,鼠标等
存储器:我们日常所说的内存
中央处理器:日常所说的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;
};
与我们之前所学的双向链表不同,它的双向链表只有前驱节点和后驱节点,这样的设计可以使进程能被多个双向链表所共有,可以很好地将进程在不同的队列(阻塞队列,运行队列等)进行来回切换,以及被多个队列所共有,后面会有所讲述。
这种设计是如何得到进程对象的地址呢?
很精妙的想法,计算链表指针在该结构体中的偏移量,用该链表地址 - 偏移量就是结构体对象的地址,进行强制类型转化从而得到结构体对象。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)