1. 进程
进程就是一段程序的执行过程。
进程与程序的区别
- 程序是静态的,它是保存在磁盘上的指令的有序集合,没有任何执行的概念
- 进程是一个动态的概念,它是程序执行的过程,包括进程的创建、调度和消亡
- 程序是保存在磁盘上的,而进程是在内存中运行的
2. 进程空间问题
当一个进程运行或者创建时,操作系统会自动为其分配4G的虚拟内存空间(32位操作系统),虚拟内存的使用主要解决了进程间通信问题,保证每一个进程的空间一样,这样在通信的时候,数据交换更加方便。
既然说是分配4G的虚拟内存,那么实际分配时一定不会直接给它4个G的物理内存,而是实际使用多少,就分配多少的物理内存。
2.1 虚拟内存的划分
- 4G 的虚拟内存分为 1G 内核空间 和 3G 用户空间
- 1G 的内核空间是 操作系统中所有进程所公有的
- 3G 的用户空间是进程私有的
进程间通信就是学习如何在内核空间开辟区域。
2.2 虚拟内存和物理内存之间由MMU(内存管理单元)映射管理
MMU(Memory Management Unit)主要用来管理虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权、多任务多进程操作系统。
2.3 虚拟内存划分
2.4 进程进行IO操作示意图
3. 进程相关概念
3.1 进程号 PID
在操作系统中,每一个进程都有一个编号,该编号就是进程号,进程号是进程的唯一标识
进程号是操作系统给当前进程随机分配的,非负整数。
特殊的进程号:
0
→ 内核进程号,系统运行起来就是内核进程1
→init
进程,它是所有进程的祖先
3.2 Linux 下进程分类
类型 | 解释 |
---|---|
交互进程 | 由shell 控制和运行,可以在前台运行,也可以在后台运行 |
批处理进程 | 不属于某一个终端,它被提交到一个队列中,以便顺序执行 |
守护执行 | 在后台运行,一般在Linux启动时就开始执行,系统关闭时才结束 |
3.3 进程控制块(PCB)
进程在创建和运行时,会有专门的一个结构体来保存它的信息,这个结构体为task_struct,又将其称之为进程表项或进程控制块,简称PCB
在/usr/src/linux-headers-3.2.0-29-generic-pae/include/linux下的sched.h头文件里面定义了task_struct结构体
3.4 进程的调度机制
多个进程如何运行
时间片轮转,上下文切换
3.5 进程的运行状态
符号 | 状态 | 解释 |
---|---|---|
D |
等待 | 不可中断的静止 |
R |
运行态 | 正在执行中 |
S |
睡眠态 | 阻塞状态 |
T |
停止态 | 暂停执行,程序运行时,按下ctrl +Z ,进程可进入该状态 |
Z |
僵尸态 | 不存在但无法消除 |
W |
没有足够页可分配 | |
+ |
前台进程 | 前台运行 |
< |
高优先级进程 | 优先运行 |
N |
低优先级进程 | |
L |
有内存分页分配并锁在内存中,多线程中出现 | |
s |
会话组组长 |
4. 进程相关命令
查看进程,杀掉进程
ps #查看当前用户运行的进程
ps aux #查看当前系统中所有的进程,并且可以查看所占内存百分百等信息
ps ajx #查看当前系统中所有的进程,并且可以查看当前进程的父进程的id
top #动态显示系统中所有的进程
htop #更加好看的动态显示系统中所有的进程
sudo apt-get install htop #下载htop
nice #按用户指定的优先级运行进程
renice #改变正在运行进程的优先级
kill #向一个进程发送一个信号
kill -9 pid #将进程号为pid的进程杀死
pstree #以树形结构显示系统中所有的进程的关系
前后台切换
#将一个进程在后台运行,查看运行状态后面没有+,如果是前台运行,会有+
./a.out &
#将挂起的进程(停止态T)在后台执行
bg
#把后台运行的进程放到前台运行(+),也可以直接把停止态(T)的进程直接唤醒
fg
#将进程号为pid的进程变为停止态
kill -19 pid
#将进程号为pid的进程从停止态变为后台进程
kill -18 pid
5. 进程相关函数
5.1 fork() 创建子进程
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:
父进程创建一个子进程
参数:
无
返回值:
成功:
>0 子进程的进程号,标识父进程的代码区
0 标识子进程的代码区
失败:-1
fork函数主要用于在一个进程中创建子进程,目的是为了能够在一个程序里面分别独立执行多个任务,相互之间还没有影响
只要执行一次fork()
,就会在原有进程基础上再创建一个进程
如果不区分父子进程代码,fork()
后所有代码,父子进程都会执行
一般使用fork()
都是为了执行不同任务,所以一般都会区分父子进程代码区。
区分父子进程的方法就是判断fork()
函数的返回值,在父进程中,fork()
返回子进程PID
,在子进程中fork()
返回0
。
父子进程调度机制
父子进程调度机制也是时间片轮转,上下文切换。
所以,他们之间执行时,根本没有先后之分。
父子进程用户空间问题
使用fork()
函数创建的子进程,会将父进程 (虚拟内存中) 的用户空间完整复制一份,作为子进程 (虚拟内存中) 的用户空间。
父子进程之间的用户空间是完全独立的,他们对各自用户空间的操作,都不会对对方产生任何影响。
这里注意,如果在父进程中已经定义了一个变量a
,a
的地址为0x123
,当子进程创建完后,子进程中也会有变量a
,且地址也是0x123
,但是他们并不是同一个a
,因为他们分别属于不同的用户空间,互不影响。
父子进程共享资源问题 / 父子进程同时访问同一文件
先看一段代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd;
if((fd = open(argv[1], O_RDWR)) == -1)
{
perror("open error");
return -1;
}
pid_t pid = fork();
if(pid == -1)
{
perror("fork error");
return -1;
}
else if(pid > 0) //父进程的代码区
{
//父进程向文件写入数据
write(fd, "hello world", 12);
printf("父: [%d]的offset = %ld\n"