一、了解进程推荐看这个视频,很详细
1、概念
- 进程(Process)=程序的运行过程,是系统进行资源分配和调度的独立单元
- 程序的运行过程:多个不同程序 并发,同一个程序同时执行多个任务。
- 就需要很多资源来实现这个过程。
- 每个进程都有一个独立的地址空间【空间由0开始扩大,直到最大值,成一个连续的空间】。在这个空间里,进程能够执行读写操作,存放可执行的程序代码、程序运行所需的资源以及函数调用和局部变量存储的栈结构。
- 进程控制块(PCB):
- 是一个数据结构。
- 描述进程的基本情况和运行状态。
- 为控制和管理提供数据依据。
2、访问linux的进程方法
那么如何查看进程?
基本语法:ps 选项
a:显示当前终端下的所有进程信息,包括其他用户的进程
u:以用户为主的进程状态
x:通常与 a 这个参数一起使用,显示当前用户在所有终端下的进程信息
-e:显示系统内所有的进程信息
-l:使用长格式显示进程信息
-f:使用完整的格式显示进程信息
//使用ps -aux
ps -aux
ps -elf
top
q 退出显示
top -d 默认三秒,指定top命令每隔几秒刷新
top -i 使用top不显示任何闲置或者僵死的进程
top -p 通过指定监控ID来仅仅监考某个进程的状态
例如:
top -d 10 每10秒刷新1次
- top命令将会在当前终端以全屏交互式的界面显示进程排名,及时跟踪CPU、内存等系统资源占用情况,默认情况下每三秒刷新一次,其作用类似于windows系统中的任务管理器。
pgrep
- 使用pgrep命令可以根据进程的名称、运行该进程的用户、进程所在的终端等多中属性查询特定进程的PID号。
pstree 选项
-u 显示进程的用户名
-p 显示进程号(PID)
- 将进程以树形的结构展现出来
3、删除进程
kill 进程号 删除进程
kill -9 进程号 强制删除进程
4、进程的文件
- 在**/proc/里面**
这些蓝色的就是进程号。
二、进程模型
严格来说,一个CPU核心在任一时刻仅能执行一个线程的任务,即便是系统配置有双核心(或更多CPU)时,每个核心也是独立地且一次仅执行一个线程。
- 进程的组成
- 1、代码段和相关数据段
- 2、PCB(进程控制块)
- 2.1、PCB包含的信息
- ①进程标识符PID
- ②进程控制管理信息
- ③资源清单
- ④进程调度信息
- ⑤处理机状态
- 2.2、PCB是什么?
- ①是一个数据结构
- ②描述进程的基本情况和运行状态
- ③为管理和控制提供数据依据
- 从抽象的角度看,每个进程似乎都配备了专属的虚拟CPU以支持其独立执行,但物理程度上,CPU通过时间片机制在多个进程间进行快速切换,从而并发执行的假象。【简而言之:抽象看是每个进程都有一个CPU;物理上看是只有一个CPU供所有进程使用,进程用时间片机制来快速切换,像并发一样。】
-
如下图【物理上看】:
- 所以可以发现:
- 在经历足够长的一段时间后,所有的进程都运行了,但实际上在任何一个给定的瞬间仅有一个进程真正运行。
- 所以它们实际上是这样运行【A运行一点时间,就给B运行,B运行一点给C运行,如果需要回到A,就使用PCB,PCB记录了之前的状态。】的,如下图:
- PCB进程控制管理信息
- 代码段和数据段的地址
- 进程同步和通信信息
- 资源清单地址
- 进程队列指针
- PCB处理机状态
- 处理机中寄存器的内容
- 作用:CPU切换时保存现场信息和回复现场信息。
- PCB进程标识符
- 进程号
- PCB资源清单
- 内存地址
- 虚拟地址
- 打开文件列表
- I/O设备信息
- PCB进程调度信息
- 进程状态
- 优先级
- 等待和使用CPU时间总和
- 进程调度和对换依据
- 进程与进程之间怎么数据交换?
-
依靠操作系统内核
-
-
但是从上图可知:进程1和进程2是独立的个体。似乎不相互影响。
-
- 我们通过2段代码来理解【推荐先看下面部分的编程再看这里】
- 首先是创建一个全局变量 global_vary = 100 。再在child函数里改global_vary =200 。然后我们运行代码,得到结果如下图:
- 会发现,parent还是100 。child的是200 。
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int global_vary = 100;//创建了一个全局变量
void child(){
global_vary =200;
printf("in child process:%d global_vary:%d \n",getpid(),global_vary);
}
void parent(){
sleep(3);//休眠3秒后,再运行。
printf("int parent process:%d global_vary:%d \n",getpid(),global_vary);
}
int main(){
pid_t pid;
pid=fork();
if(pid<0){
perror("fork have problem\n");
exit(0);//释放pid
}else if(pid==0){//child
child();
}else if(pid>0){
parent();
}
return 0;
}
- 使用sleep 修改
- 让 子进程结束变慢。
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int global_vary = 100;//创建了一个全局变量
void child(){
global_vary =200;
printf("in child process:%d global_vary:%d \n",getpid(),global_vary);
sleep(3);
}
void parent(){
sleep(1); printf("int parent process:%d global_vary:%d \n",getpid(),global_vary);
}
int main(){
pid_t pid;
pid=fork();
if(pid<0){
perror("fork have problem\n");
exit(0);//释放pid
}else if(pid==0){//child
child();
}else if(pid>0){
parent();
}
return 0;
}
就会发现结果还是 200 ,100 。
这就能体现出他们是独立的。
它们是独立占用 0-3G虚拟空间。
三、进程的创建
1、了解情况
- 随机分配,谁先获得CPU的使用权。
- 通过返回值来区分父子进程。
- 返回是2次,一次是父进程的,另一次是子进程的。
- 子父进程共享文件
2、简单编程实现
2.1、getpid和getppid和fork
fork()
的作用:- fork() 会创建一个子进程,父进程返回子进程的 PID,子进程返回 0。
- 如果 fork() 失败(如系统资源不足),返回 -1,并进入错误处理。
getpid() 和 getppid()
:- getpid():返回当前进程的 PID。
- getppid():返回当前进程的父进程 PID。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork failed\n");
exit(1);
} else if (pid == 0) {
printf("子进程: PID = %d, PPID = %d\n", getpid(), getppid());
} else {
printf("父进程: PID = %d, PPID = %d\n", getpid(), getppid());
wait(NULL); // 父进程等待子进程结束
}
return 0;
}
2.2、sleep
如果我想让子进程先运行,父进程后运行。就可以使用sleep(时间)
。这里的时间是默认按秒的时间,休眠作用。
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
void child(){
printf("in child process:%d \n",getpid());
}
void parent(){
sleep(3);//休眠3秒后,再运行。
printf("int parent process:%d \n",getpid());
}
int main(){
pid_t pid;
pid=fork();
if(pid<0){
perror("fork have problem\n");
exit(0);//释放pid
}else if(pid==0){//child
child();
exit(0);
}else if(pid>0){
parent();
exit(0);
}
return 0;
}
2.3、execlp和excvp
execvp
和execlp
很像,只是参数的写法不同。- 都通过PATH的方式查找。
- execvp的参数是指针数组
- execlp的参数是可变参数列表
#include <unistd.h>
int main() {
execlp("ls", "ls", "-l", NULL); // 等价于 execvp("ls", {"ls", "-l", NULL})
perror("execlp failed");
return 1;
}
- exlvp的作用:
- 用于 替换当前进程的映像(即运行另一个程序,当前进程的代码和数据会被新程序覆盖 。替换后,原进程的代码不会执行了。)。
函数原型
#include <unistd.h>
int execvp(const char *file, char *const argv[]);
argv以NULL结尾
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
pid_t pid;
pid=fork();
if(pid<0){
perror("fork have problem\n");
exit(0);//释放pid
}else if(pid==0){
//替换成当前父进程的上下文,替换成新的进程静态数据区
int ret=execlp("ls","ls","-l",NULL);
if(ret<0){
perror("execlp error");
exit(-1);
}
printf("================================\n");
exit(0);
}else if(pid>0){
//父进程回收子进程的资源
getchar();
wait(NULL);//阻塞接口,等待一个事件的发生
}
return 0;
}
3、创建进程中的指令总结
exit(数字) 用来终止执行
fork 创建进程
wait 接收另一个进程的退出[阻塞接口]
sleep 休眠
execlp 替换当前进程的映像
- fork 子进程诞生,此时父进程是副本
- exec系列调用,替换成静态区
- exit系统调用,结束,变成僵尸
- wait系统调用,回收资源,已经变成僵尸状态的子进程的资源。