1.进程
在讲多线程前,我们先了解一下进程
打开任务管理器,我们可以看到此处便有“进程”二字
不妨猜一下进程是什么呢? 在任务管理器中我们可以看出来进程正对应着我们所打开的程序
因此进程(process)正是正在运行的程序
在操作系统中进程是操作系统进行资源分配和调度的基本单位
我们知道了进程是正在运行的程序,而对于计算机来说,我们可以运行上百个来个进程,一旦进程过多便涉及到进程的管理问题
右键进程属性栏可以看到进程有多个属性
对于这种多属性变量我们采用结构体描述
struct task_struct {
pid_t pid; // 进程ID(唯一标识)
pid_t tgid; // 线程组ID(对主线程等于pid)
struct task_struct *parent; // 父进程指针
struct list_head children; // 子进程链表
struct list_head sibling; // 兄弟进程链表
...
};
以Linux系统的进程结构体(这个结构体我们称之为进程控制块 Process Control Block,简称PCB)为例,在Linux系统中以链表(双向循环链表)的形式管理进程,每当有新的进程便将其添加到链表中,进程销毁对应链表位置便进行删除,以此将多进程串在链表上
重要的进程属性
下面我们介绍几个重要的进程属性
1.PID(Process Identifier)
PID即进程身份标识符,作为进程的唯一标识符标识当前进程
PID如图所示
2.内存指针
内存指针指向该进程需要执行的指令与所需处理的数据(这个内存指针是一组指针,不过对于Java程序员并不需要过多了解指针,点到为止)
以创建一个进程操作系统的加载过程讲解,假设打开qq程序
1)双击exe可执行文件2)系统加载可执行文件中保存了程序运行过程需要执行的二进制指令(程序员编写的代码经过编译后生成的是二进制文本文件)
而二进程指令的执行过程又依赖于数据,因此这些都需要内存指针保存
3.文件描述符表(File Descriptor Table)
进程运行过程中是需要和硬盘这种硬件设备交互的,而硬盘上的资源正是通过文件的形式进行组织管理的。(在操作系统中遵循着“一切皆文件”的设计理念,因此需要文件描述符表来协助管理文件、套接字、终端等I/O设备)
当进程打开一份文件时,操作系统便会将文件信息写入文件描述符表以便于管理I/O
进程调度
了解了以上进程属性,我们来想一个问题
cpu的核心数是有限的,以我自己的电脑为例,仅有12个核心,但这12个核心却要处理上百个进程,这是怎么做到的呢?
这就与cpu对于进程的调度有关了
我们知道cpu是可以高速运转工作的,因此在有限的核心数的情况下,cpu便是将一个时间片(极短,10--100ms)分成多份,每份时间片内调用不同的进程工作以此实现宏观上的同时调用不同程序
这种形式我们称之为“分时复用技术”,而这种宏观上的多任务同时执行我们称之为“并发”;
与并发相应的还有种执行方式,即“并行”,并行便是真正意义上的cpu同时执行多个任务。
正是因为需要并发执行,所以操作系统需要多次切换进程,即进程调度
这里便涉及到在PCB中与进程调度相关的属性
1.进程状态
enum task_state {
TASK_RUNNING, // 运行中或就绪
TASK_INTERRUPTIBLE, // 可中断睡眠(等待事件)
TASK_UNINTERRUPTIBLE, // 不可中断睡眠(等待IO)
__TASK_STOPPED, // 暂停(如被SIGSTOP信号暂停)
__TASK_TRACED, // 被调试器跟踪
...
};
进程有两个状态:1.就绪状态,即随时可以运行投入任务;2.阻塞状态,当前进程在执行或等待等原因不适合在cpu上工作即为阻塞状态
2.进程优先级
struct task_struct {
int prio, static_prio, normal_prio; // 优先级
unsigned int rt_priority; // 实时优先级
struct sched_entity se; // CFS调度实体
struct sched_rt_entity rt; // 实时调度实体
...
};
在此先明确一点,进程执行任务是需要消耗cpu资源的,因此所有进程到cpu上执行任务的机会
并不是均等的而是有优先级的。
3.进程的上下文
struct task_struct {
struct thread_struct thread; // 硬件上下文(寄存器值)
...
};
前面的进程状态中有谈到进程是有阻塞状态的,而进程阻塞后cpu是需要去调用下一个进程的,但当下一个进程完成了或也进入阻塞了,那么上一个进程需要被唤醒呢?这个时候我们就需要查看进程的上下文来唤醒上一个进程,让它从阻塞处继续执行而不能是重新执行(可以理解为存档读档)
4.进程的记账信息
struct task_struct {
cputime_t utime, stime; // 用户态/内核态CPU时间
unsigned long nvcsw, nivcsw; // 自愿/非自愿上下文切换次数
struct timespec start_time; // 进程启动时间
...
};
记账信息即统计功能,统计当前进程在cpu上吃了多久资源、多久没有吃到资源等信息,以此辅以cpu对进程的调度