0.问题引入
程序 = 数据结构 + 算法
数据:用来表示人们思维对象的抽象概念的物理表现叫数据
而数据处理的规则叫操作(指令)
对某一个优先数据集合所实行的,目的在于解决某一个问题
的一组有限指令集合,称之为计算
(1)顺序执行
一个程序执行完毕之后,才能执行下一个程序
例如一个程序分为三个步骤
输入数据-》计算-》写回文件
缺陷 CPU利用率非常低
(2)并发执行
把指令的执行过程,分成一下几个不同的步骤
取指----》执行------>写回
不同的步骤是由不同的硬件完成的
这样就可以多个程序同时执行
为了提高cpu的利用率,增加吞吐量“并发执行”
现代操作系统为了能让程序并发执行,特定引入“进程”的概念
1.进程是个什么东西?
进程是具有独立功能的程序关于某个集合上的依次运行活动
test.c -----> 源程序
int main()
{
int a,b;
int sum;
scanf("%d %d",&a,&b);sum = a+b;
printf("sum = %d\n",sum);
return 0;
}
gcc test.c -o test =>test 可执行程序./test =》进程
2.进程和程序的区别
(1)程序是一个静态的概念(是指令的有限集,“程序文件”)
进程是一个动态的概念
(2)进程是一个程序的一次执行活动
一个程序可以对应多个进程
(3)进程是一个独立的活动单位
进程是竞争系统资源的最小单位
e.g
程序是一个菜谱
进程是炒菜的过程
OS为什么要引入进程呢》
就是为能让程序执行并发(同一时段有多个进程在运行)
进程是如果何让多个程序并发执行呢?
程序的并发实际就是进程的并发。
进程是如果同时运行,实现并发呢?
3.进程的状态
OS把一个进程的执行过程,分为一下几个阶段
就绪态(Ready) 准备工作已经做好了,只要有CPU就可以执行
运行态(running) CPU正在执行这个进程的指令
阻塞态(Blocking 等待waiting) 进程在等待其他的外部事件
进程可以在这几个状态进行切换
就绪队列 Ready Queue
所有处于Ready的状态的进程,都在一个这个队列中
“调度策略”“:"调度算法"
分时系统:调度策略是以“时间片轮转”为主要策略
“时间片轮转” 每个进程执行一段时间(时间片)
时间到了或者进程自己放弃就会进入到waitting
如:大部分桌面系统 windows android linux macos unix...
实时系统
调度策略以实时策略为主要策略的系统
实时策略:每次调度都去优先级最高的那个进程执行,
直到这个进程执行完毕或者它主动放弃CPU
或者有其他更高优先级的进程抢占
如 ucos freeRTOS
进程1 优先级4 进程2 优先级3
跑进程1
进程1执行1秒中之后 主动放弃了CPU 进入waiting状态
跑进程2
进程1经过1s后,有进入到就绪队列
跑进程1 进程2会被打断
进程1 优先级4 进程2 优先级4
跑进程1
进程1执行1秒中之后 主动放弃了CPU 进入waiting状态
跑进程2
进程1经过1s后,有进入到就绪队列
跑进程2 进程2不会被打断
"抢占" :插队 不论是实时还是分时系统都具有抢占的特性
思考: OS引入进程后 是如何实现程序并发执行的
程序执行的过程 =》进程
程序执行和分配资源的最小单位 =》进程
程序 = 数据 + 指令
进程要做的第一件时间 就是申请一块内存空间来存储程序
不同的程序数据的属性是不一样的,需要去分区域
存储程序的数据。
4.linux进程地址空间布局
“分段” 分成了不同的逻辑区域
Linux对进程的数据仅分段管理,不同属性的数据存储的
内存段也不同,不同的内存段(内存区域)的属性及管理的方法不一样
.text
主要是存放代码。
只读且共享,这段内存在程序运行期间不会被释放的
“代码段” 随进程持续性
.data
数据段
主要存放程序中已经初始化的全局变量和已经初始化的static修饰的变量
可读可写 这段内存在运行一直存在 随进程持续性
.bss
数据段
主要存放程序中没有初始化的全局变量和没有初始化的static修饰的变量
可读可写 这段内存在运行一直存在 随进程持续性
.bss段在进程初始化时,(可能)全部初始化为0;
.rodata
只读数据段
主要存放程序中的只读数据(常量)
只读 随进程持续性
栈空间(stack)
主要保存局部变量(不是static修饰的局部变量)
可读可写。时段空间会自动释放(变量所在的代码段执行完了,代码块中的局部变量
的空间就是自动释放)随代码块持续性 返回局部变量的地址时有问题 也是这个原因
堆空间(heap) 动态内存空间
主要时malloc/realloc/calloc动态分配的空间
可读可写,这段内存在进程运行期间,只要分配,就一直存在
直到你手动free 或者进程消亡
内存泄漏/垃圾内存
5.linux下进程相关的API
(1)创建一个新的进程
fork
用户数据
fork一个新进程的时候 这个新的进程的数据和指令来源哪里?
来源它爸(父亲,调用fork那个进程) fork这个函数在创建子进程时:
copy 父进程的数据和指令
父进程的变量 数据对象
标准IO缓冲区
文件描述符
。。。
copy完了后 父子就独立了
fork成功时,会有两个进程在执行当前的代码!!!!
所以为了区分父进程还是子进程 ,fork一次调用会有两个返回
一个是父进程的返回
一个是子进程的返回
通过fork的不同的返回值 来区分到底是父进程返回还是子进程返回
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>pid_t fork(void);
返回值
如果失败返回-1 同时errno被设置
如果成功
父进程返回 子进程的pid(>0)
子进程返回 0
思考题
fork一旦成功 就会有父子进程,那么fork之后到底是父进程
先执行还是子进程先执行 不直到取决于调度算法
fork子进程会拷贝附近的指令的数据 到底拷贝了那些数据呢?
a:父进程全部的用户数据
b:父进程打开的文件描述符及状态
c:标准IO缓冲区
d:信号的处理方式
.... fork用来创建一个新的进程(child process), 要创建新的进程
首先要知道一个进程里面包含了一些什么东西
指令
系统数据
linux系统会为每一个进程,分配一个唯一的进程ID(>0的整数),pid_t的类型来描述
而且还提供了两个函数用于获取当前进程(自己)以及父进程的pid;
NAME
getpid, getppid - get process identificationSYNOPSIS
#include <sys/types.h>
#include <unistd.h>
//获取当前进程的pid
pid_t getpid(void);
//获取父进程的pid
pid_t getppid(void);
(2)进程退出
进程退出有两种情况
(2.1)自杀(自己退出)
a.main函数返回 进程退出
b.在进程执行时,自己调用了进程退出函数
(2.2)他杀 被操作系统给干掉了
NAME
exit - cause normal process terminationSYNOPSIS
#include <stdlib.h>
void exit(int status);
int status:退出码 表示退出时的状态
退出码含义 由程序员解释 自己决定退出码的含义
正常跑路
exit 正常退出 做清理工作(如把缓冲区的内容同步到文件中去)
NAME
_exit, _Exit - terminate the calling processSYNOPSIS
#include <unistd.h>void _exit(int status);
int status:退出码 表示退出时的状态
退出码含义 由程序员解释 自己决定退出码的含义
比如: 你走进传销了,得赶紧跑路
中止进程,来不及做清理工作
(3)等待子进程退出
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
这两个函数用来等待某个(些)子进程的状态方式改变的,等待的状态
发生改变有三种情况
a.子进程退出(正常退出:main函数结束 函数结束exit/_exit)
b.子进程被信号终止
//c.子进程被信号唤醒(waiting ->ready)
在子进程正常退出的情况下,调用wait/waitpid可以释放子进程的资源,
没有调用wait/waitpid,那么子进程退出后,就会变成僵尸进程
一个进程的退出,操作系统会释放掉他大部分的资源,但是有一部分
必须留给他父进程去释放,如果一个进程退出了,但是它父进程没有
wait/waitpid,那么这个进程就会变成僵尸进程:已经死掉了,但是
资源没有完全被释放
如果一个子进程的状态已经发生了改变,那么调用wait/waitpid就会立即返回
否则则阻塞调用进程,直到子进程的状态发生了改变或者信号中断
wait: 用来等待任意一个子进程退出或状态方式改变的
如果一个附近有多个子进程,你需要多次调用
wait来等待所有的子进程结束
pid_t wait(int *wstatus);
int *wstatus:int类型指针 指向的空间用来保存子进程的退出信息
(退出码,怎么死的)
返回值:
成功返回退出的那个子进程的pid
失败返回-1 同时errno被设置
status: 用来保存子进程的退出信息,退出信息保存在一个整数上的。
WIFEXITED(status)
加入这个进程是正常退出(情况a) 则返回true
只有子进程正常退出 才会有退出码
WEXITSTATUS(status)
返回子进程的退出码,只有子进程正常退出
这个宏才有意义
退出码是当作unsigned char 来看待的
WIFSIGNALED(status)
return ture 这个进程是被信号给干掉的
pid_t waitpid(pid_t pid, int *wstatus, int options);
pid_t pid: 指定要等待的进程或进程组状态发生改变
pid == -1 表示等待调用进程的任意子进程
pid == 0 表示等待与调用进程同组的任意进程
进程组: 就是一组进程。每一个进程必须会属于某一个进程组
并且每个进程组都会有一个组长进程,一般来说,组长进程
就是创建这个进程组的进程 进程组有一个组ID
这个组ID就是组长进程ID
pid < -1 表示等待组id等于pid绝对值的那个组的任意子进程
如果 pid = -1234
表示等待组id为1234哪个组的任意子进程
pid > 0 表示等待指定的子进程 其进程id就是pid
int *wstatus status: 同上
int options: 等待选项
0:表示阻塞等待
WNOHANG:非阻塞等待 如果加入子进程没有结束 则立即返回,不等待
如果结束了,则就返回该进程的pid
返回值 成功返回退出进程的pid
失败返回-1 同时errno被设置
wait(&status) =>waitpid(-1,&status,0)
(4)exec函数族
fork一个进程 一般来说 目的是让进程去执行其他的任务
exec函数族的主要功能是 让进程去执行指定的程序文件
exec函数族让指定的程序文件中的数据和指令替换掉
调用进程的数据和指令
exec函数族让一个进程去执行另外一个程序 那么
要指定程序文件的名称
在文件系统中程序文件名称 (路径)
还得指定程序运行的参数
linux下面程序的参数 都是字符串
指定程序的参数有两种方式
l:list
把程序运行的参数 一个一个的列举出来
程序运行的第一个参数 是程序的名称(不带路径) "sum","2","4",NULL
v:vector 向量 数组
把程序运行的参数,弄成一个数组 char*
char *argv[] = {"sum","2","4",NULL}
NAME
execl, execlp, execle, execv, execvp, execvpe - execute a fileSYNOPSIS
#include <unistd.h>extern char **environ;
execl 让进程去执行参数指定的程序文件
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
const char *path:你要执行程序的文件名(带路径)
const char *arg,..:程序运行的参数
如
"sum","2","4",NULL
返回值 失败返回-1 同时errno被设置
成功 就不会有返回,因为都替换了
指令和代码都被别人给替换了 哪来的返回值
int execv(const char *path, char *const argv[]);
char *const argv[]:指定程序运行的参数的数组 最后一个为NULL表示参数结束了
系统中有一个东西 环境变量 PATH
环境变量是整个系统环境内所有进程共享的变量
有很多个环境变量 其中过一个叫 PATH
PATH:=dir1:dir2:dir3
PATH的作用是 指定命令或程序的搜索路径。
当只指定一个命令或程序的文件名,而没有指定路径时
那么系统就先回去dir1下面找 如果没有找到 则去dir2....
如果程序文件或命令已经在PATH指定的搜索目录下了
指定文件的时候就没有必要指定路径
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execvp(const char *file, char *const argv[]);
const char *file :要执行的程序文件名(可以不带路径)
-------------
fork() ->exec*
有没有一步到位函数组
(5)system
NAME
system - execute a shell command
用来执行command指定的程序或命令
system 会等待命令或程序执行完成
SYNOPSIS
#include <stdlib.h>int system(const char *command);
const char *command:字符串 表示要执行的命令 和在终端上面敲的一样
总结
进程的概念
进程和程序的区别
进程的状态
调度策略
实时系统
分时系统
抢占
就绪队列
fork的实现原理
1.复制
2.独立
exec函数族