linux-多进程基础(1) 程序、进程、多道程序、并发与并行、进程相关命令,fork
程序是什么
程序是包含一系列信息的文件。这些信息描述了如何在运行时创建一个进程,包含二进制格式标识、机器语言指令、程序入口地址、数据、符号表及重定位表、共享库信息及其他信息
- 二进制格式标识,每个程序包含了描述可执行文件的元信息(是否可读之类的),内核利用这个信息来解释文件中给的其他信息(ELF可执行连接格式,<-知道就行)
- 机器语言指令:对程序进行编码
- 程序入口地址:main()
- 数据:程序中的初始变量值和字面量值,例如字符串什么的
- 符号表及重定位表:描述程序中函数和变量的位置及名称。这些表格有很多用途,例如调试和运行时的符号解析(动态链接)
- 共享库和动态链接信息:就是共享库的有关信息,这部分不在代码里,要到内存里去找
- 其他信息:
字面量值:就出现了的都属于那种字面量,就露面了的。int a = 10这个就是
进程是什么
简而言之:进程是正在运行的程序的实例,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是分配单元也是执行单元。进程也是由内核定义的抽象实体(存在,也不存在)。
程序与进程的关系:一个程序可以用来创建多个进程。程序是一个文件,占用磁盘的空间,进程运行的时候,内核会分配CPU和内存给进程当作资源。
从内核的角度看,进程是由用户内存空间和一些内核数据结构组成。
- 用户内存空间:包括一些代码、变量
- 内核数据结构:维护进程状态的信息,这些信息包含与进程相关的标识号、虚拟内存表、文件描述符表、信号传递及处理有关信息等信息。
关于多道程序设计
首先单道程序,即再计算机内存中只允许一个的程序运行
而多道程序设计为:在内存中存放多个相互独立的程序,使他们在管理程序之下,能够相互穿插运行,多道程序同时处于开始和结束的状态,可以提高CPU的利用率
对于一个单CPU来说,同时运行多个程序是宏观的状态,在微观时间范围下,一个CPU上的运行的程序只有一个。
CPU一秒能运行10亿条指令,所以看似同时运行
时间片
又称为“量子”或者“处理器片”,是操作系统分配给每个正在运行的进程微观上的一段CPU时间。时间片通常很短,在5ms-800ms
时间调度策略:(可以之后再补充)
- 时间片由内核的调度程序分配给每个进程。首先内核给每个进程分配相等的时间片,如何没进程轮番执行相应的时间,当所有时间片都处于耗尽状态,内核再重新为进程分配时间片,如此往复。
并发与并行
并行(parallel):指同一时刻有多条指令在多个处理器上同时执行
并发(concurrency):指同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得宏观上有多个进程同时执行的效果(PS:就像CPU的多道程序设计)
PCB进程控制块
这个就是虚拟地址空间的内核数据里面的进程管理里面的,是一个task_struct结构体。它包含以下内部成员:(就回想哪些跟进程有关的)
- 进程ID
- 进程的状态:有就绪、运行、挂起、停止
- 进程切换时需要保存和恢复的一些CPU寄存器
- 描述虚拟地址空间的信息
- 描述控制终端的信息,就每个进程有个对应的终端
- 当前工作目录
- umask掩码
- 文件描述符表
- 和信号相关的信息
- 用户id和组id
- 会话(session)和进程组
- 进程可以使用的资源上限
进程的状态(面试会问到)
三态模型:进程状态分为三个基本状态:就绪态、运行态,阻塞态
五态模型:新建态、就绪态、运行态,阻塞态、终止态
进程相关命令
ps aux / ajx
-a:显示终端上所有进程,包括其他用户的进程
-u:显示进程的详细信息
-x:显示没有控制终端的进程
-j:列出与作业控制相关的信息
top
实时显示进程动态,可以在使用top 加上-d来指定更新信息的时间间隔, 在top执行之后,可以输入以下案件对结果排序和筛选
-M 根据内存使用量排序
-P 根据CPU占有率排序
-T 根据进程运行时间长短排序
-U 根据用户名来筛选进程
-K 输入指定的PID杀死进程
kill [-signal] pid
kill -l
:列出所有信号
kill -9 进程ID
等价于kill –SIGKILL 进程ID
killall name
:根据进程名杀死进程
进程号和相关函数
- 每个进程都由进程号来标识,其类型为
pid_t(整型)
,进程号的范围:0~32767
。进程号总是唯一的,但可以重用。当一个进程终止后,其进程号就可以再次使用 - 任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进程号(PPID)
- 进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进程有一个进程组号(PGID)。默认情况下,当前的进程号会当做当前的进程组号
- 进程号和进程组相关函数
pid_t getpid(void);
:获取进程IDpid_t getppid(void);
:获取进程的父进程IDpid_t getpgid(pid_t pid);
:获取进程的组ID
进程创建
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
函数的作用:用于创建子进程。
返回值:
fork()的返回值会返回两次。一次是在父进程中,一次是在子进程中。(即,在父进程中返回子进程ID,在子进程中返回0)
如何区分父进程和子进程:通过fork的返回值。
在父进程中返回-1,表示创建子进程失败,并且设置errno
eg:
int num = 10;
// 创建子进程
pid_t pid = fork();
// 利用返回的pid判断是父进程还是子进程,执行对应的代码
if(pid > 0) {
printf("pid : %d\n", pid);
// 如果大于0,返回的是创建的子进程的进程号,当前是父进程
printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
printf("parent num : %d\n", num);
num += 10;
printf("parent num += 10 : %d\n", num);
} else if(pid == 0) {
// 当前是子进程
printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
printf("child num : %d\n", num);
num += 100;
printf("child num += 100 : %d\n", num);
}
/*西面是父进程和子进程共享的代码,会交替运行*/
// for循环
for(int i = 0; i < 3; i++) {
printf("i : %d , pid : %d\n", i , getpid());
sleep(1);
}
那么得到的结果会是:
- 父进程的代码和子进程是一样的
- 两个进程的变量及其值是互不相干的(num没有变)
fork的原理(面试会问到)
-
Linux 的
fork()
使用是通过写时拷贝 (copy- on-write) 实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术 -
内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间,只有在需要写入的时候才会复制地址空间,从而使各个进程拥有各自的地址空间。即资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享(示例程序中
num
的作用) -
fork之后父子进程共享文件。fork产生的子进程与父进程有相同的文件描述符,指向相同的文件表,引用计数增加,共享文件偏移指针
*真拷贝了,用户数据都会拷贝,内核数据也会拷贝只是pid也就是进程ID变了
父子进程关系
区别
- fork()函数的返回值不同。父进程中: >0 返回的是子进程的ID,子进程中: =0
- pcb中的一些数据不同。pcb中存的是当前进程的ID(pid),当前进程的父ID(ppid)和信号集
共同点
- 在某些状态下,即子进程刚被创建出来,还没有执行任何的写数据的操作。此时用户区的数据和文件描述符表父进程和子进程一样
父子进程对变量共享说明
- 刚开始的时候,是一样的,共享的。如果修改了数据,不共享了
- 读时共享(子进程被创建,两个进程没有做任何的写的操作),写时拷贝