C/C++进程与线程
程序运行基础部分
1、时钟中断:即为多道程序设计模型的理论基础。 并发时,任意进程在执行期间都不希望放弃cpu。因此系统需要一种强制让进程让出cpu资源的手段。时钟中断有硬件基础作为保障,对进程而言不可抗拒。 操作系统中的中断处理函数,来负责调度程序执行。
2、 CPU和MMU
2.1CPU运算过程
中央处理器(CPU)
缓存:位于CPU与内存之间的临时存储器。现在大部分的处理器都有二级或者三级缓存,缓存又可以分为指令缓存和数据缓存,指令缓存用来缓存程序的代码,数据缓存用来缓存程序的数据。
预取器:从缓存中读取数据,交给译码器解析。
译码器:对数据进行解析,并送给数据逻辑单元执行。
寄存器堆:寄存器部分,CPU所谓32位和64位是针对寄存器而言的,32位是4字节大小,64位是8字节,寄存器位于CPU内部,CUP直接从寄存器中读取数据,对数据进行计算和存储,一个寄存器的大小是4KB,32位通用寄存器有八个,eax, ebx, ecx, edx, esi, edi, ebp, esp(他们主要用作逻辑运算、地址计算和内存指针),6个段寄存器(es、cs、ss、ds、fs和gs),1个指令指针寄存器(eip),1个标志寄存器(EFlags)。
算术逻辑单元:CPU的运算部分,只支持+和<<运算,由译码器解析完后,把需要运算的数据放入到寄存器中,算数逻辑单元对数据进行+或<<运算,计算完在把结果回写到寄存器中,然后再写入缓存或内存中。
2.2、MMU内存管理单元
MMU位于CPU内部,它的作用是:
1、用来管理虚拟内存与实际的物理地址之间的映射的,没有这个硬件,就不能实现虚拟内存管理。
2、设置修改内存访问级别。MMU在完成虚拟地址与物理地址之间映射的同时还会为这段内存设置访问级别,因为虚拟内存本身分用户空间和内核空间,他们的访问权限是不一样的。Intel架构下CPU有四个访问级别0,1,2,3级,级别越高,权限越低,linux系统通常只用了0级和三级,对应内核空间(0级)和用户空间(3级)。在进程调用系统函数的时候,需要把访问权限从用户空间转到内核空间,这也是有MMU完成的。
内存管理单元MMU
3、进程控制块PCB
PCB(进程控制块): 在*nix操作系统中,进程创建后会自动在内存中产生一个空间存放进程信息,称为PCB进程控制块,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,是以结构体的形式存在的,Linux内核的进程控制块是task_struct结构体,定义在/usr/src/linux-headers-4.15.0-33/include/linux/sched.h文件中,用 grep -r "task_struct {" /usr/ 命令搜索到,可以查看struct task_struct 结构体定义(1224-1657),重点以下几部分:
* 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
* 进程的状态,有就绪、运行、挂起、停止等状态。
* 进程切换时需要保存和恢复的一些CPU寄存器。
* 描述虚拟地址空间的信息。
* 描述控制终端的信息。
* 当前工作目录(Current Working Directory)。
* umask掩码。
* 文件描述符表,包含很多指向file结构体的指针。
* 和信号相关的信息。
* 用户id和组id。
* 会话(Session)和进程组。(会话用来统一管理进程的)
* 进程可以使用的资源上限(Resource Limit)。
程序和进程
程序:是指编译好的二进制文件,在磁盘上,不占用系统资源(cpu、内存、打开的文件、设备、锁....)
进程:是一个抽象的概念,指程序在计算机中的一次执行过程,进程是一个动态的过程描述,占有计算机的资源,有一定的生命周期,在内存上执行。
并发:在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态。但任一个时刻点上仍只有一个进程在运行,各个进程相互争夺CPU时间片。
并行:多个计算机核心在同时处理多个任务,这多个任务间是并行关系
进程的创建流程
1.用户空间运行一个程序,发起进程的创建
2.操作系统接受用户申请,开启进行创建
3.操作系统分配计算机资源,确定进程状态
4.将新创建的进程交给用户(应用层)使用
创建进程的函数
fork()函数:创建一个子进程。
pid_t fork(void); 失败返回-1;成功返回:① 父进程返回子进程的ID(非负) ②子进程返回 0
pid_t类型表示进程ID,但为了表示-1,它是有符号整型。(0不是有效进程ID,init最小,为1)
注意返回值,不是fork函数能返回两个值,而是fork后,fork函数变为两个,父子需【各自】返回一个。
系统调用fork()函数允许一个进程(父进程)创建一个新的进程(子进程),具体的做法是,新的子进程几近于对父进程的翻版:子进程获得父进程的栈,数据段,堆和执行文本段的拷贝,可将此视为把父进程一分为二,fork也因此得名。
demo1: 简单的进程创建
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int var = 34;
int main(void)
{
pid_t pid;
pid = fork();
if (pid == -1 ) {
perror("fork");
exit(1);
} else if (pid > 0) {
sleep(2);
var = 55;
printf("I'm parent pid = %d, parentID = %d, var = %d\n", getpid(), getppid(), var);
} else if (pid == 0) {
var = 100;
printf("child pid = %d, parentID=%d, var = %d\n", getpid(), getppid(), var);
}
printf("var = %d\n", var);
return 0;
}