从一次元世界进入二次元世界,不只是思维的改变,而是世界观的改变。
程序与进程
如果程序是菜谱,进程就是厨师烹饪;
如果程序是乐谱,进程就是乐师演奏;
如果程序是剑谱,进程就是剑客舞剑;
如果程序是棋谱,进程就是棋士复盘;
程序是静的,进程是动的。
进程与程序区别
No. | 进程 | 程序 |
---|---|---|
1 | 动态 | 静态 |
2 | 有生命周期 | 指令集合 |
3 | 只能对应一个程序 | 可以对应多个进程 |
概念
进程:程序在计算机上的一次执行过程,执行中的程序。
- 进程是一个抽象概念
No. | 组成 | 含义 | 类比 |
---|---|---|---|
1 | 一个独立的逻辑控制流 | 独占处理器 | 工人/机器 |
2 | 一个私有的地址空间 | 独占存储器系统 | 工厂 |
- 本质
- 程序在地址空间中按照代码逻辑控制流执行
- 资源分配最小单位
从代码到程序
从程序到进程
- 内核将程序读入内存,为程序镜像分配内存空间。
- 内核为该进程分配进程标志符PID。
- 内核为该进程保存PID及相应的进程状态信息。
进程控制块(PCB):保存进程控制信息
程序格式ELF
ELF(Executable and Linkable Format)文件格式,一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。
查看程序(ELF文件):readelf -S 文件名
查看进程空间大小:size 文件名
虚拟存储器/虚拟地址空间
段名 | 组成 | 来源 |
---|---|---|
代码段 | .text |
可执行文件 |
数据段 | .data bss |
可执行文件 |
堆栈段 | heap stack |
请求 |
变量 | 位置 |
---|---|
经过初始化的全局变量和静态变量 | .data |
未经初始化的全局变量和静态变量 | .bss |
函数内部声明的局部变量 | stack |
const修饰的全局变量 | .text |
const修饰的局部变量 | stack |
字符串常量 | .text |
.bss
(Block Started by Symbol)存放程序中未初始化的全局变量和静态变量,程序执行之前BSS段会自动清0。
详细资料
- ELF文件格式与相关命令
- ELF文件格式浅析
- ELF文件格式分析
进程状态
类似视频/音频播放器
No. | 状态 | 含义 |
---|---|---|
1 | 就绪(Ready) | 进程已获得到除CPU以外的所有必要的资源,获得CPU立即执行 |
2 | 运行(Running) | 程序正在CPU上执行 |
3 | 阻塞(Blocked) | 等待某个事件发生而无法执行时,放弃CPU |
如何查看进程
No. | OS | 命令 | e.g. |
---|---|---|---|
1 | Windows | tasklist |
tasklist /FI "PID eq 进程PID" |
2 | Linux | ps / pstree / top |
- |
ps命令
查看某进程
通过进程PID查看:ps -p 进程PID
通过命令行查看:ps -C 命令行
查看进程
No. | 风格 | 命令 | 属性说明 |
---|---|---|---|
1 | BSD风格 | ps aux |
a : 终端上所有用户的进程;u :以用户为中心显示详细信息,x :无终端进程 |
2 | System V风格 | ps -ef |
e :所有进程;f :树状显示 |
Unix从操作风格分为System V风格和BSD风格。它们在目录结构、脚本、命令行等方面存在一些差异。
- System V, 曾经也被称为AT&T SystemV,是Unix操作系统众多版本中的一支。它最初由AT&T开发。
- BSD(BerkeleySoftware Distribution,伯克利软件套件)是Unix的衍生系统,1970年代由伯克利加州大学(UniversityofCalifornia, Berkeley)开发。
随着一些并不基于这两者代码的UNIX实现的出现,例如Linux,这一归纳不再准确。像POSIX这样的标准化努力一直在试图减少各种实现之间的不同。
- 列表示说明
No. | 标识 | 含义 |
---|---|---|
1 | USER |
用户 |
2 | PID |
进程ID |
3 | %CPU |
进程占用的CPU百分比 |
4 | %MEM |
占用内存的百分比 |
5 | VSZ |
进程虚拟大小 |
6 | RSS |
常驻内存(内存中页的数量) |
7 | TTY |
终端ID |
8 | STAT |
进程状态 |
9 | START |
启动进程的时间 |
10 | TIME |
进程消耗CPU的时间 |
11 | COMMAND |
命令的名称和参数 |
- 进程状态标识
No. | 标识 | 含义 |
---|---|---|
1 | D | 不可中断Uninterruptible(usually IO) |
2 | R | 正在运行,或在队列中的进程 |
3 | S | 处于休眠状态 |
4 | T | 停止或被追踪 |
5 | Z | 僵尸进程 |
6 | W | 进入内存交换(从内核2.6开始无效) |
7 | X | 死掉的进程 |
8 | < | 高优先级 |
9 | n | 低优先级 |
10 | s | 包含子进程 |
11 | + | 位于后台的进程组 |
pstree命令
以树状图的方式展现进程之间的派生关系
- 安装
yum install psmisc
top命令
实时显示系统中各个进程的资源占用,类似Windows任务管理器。
Linux一切皆文件,在
/proc/
下也可以查看到进程。
如何创建进程
No. | OS | 命令 |
---|---|---|
1 | Windows | 程序名 |
2 | Linux | 程序名 |
如何杀死进程
No. | OS | 命令 |
---|---|---|
1 | Windows | taskkill /F /PID 进程标识 /taskkill /F /IM 程序名 |
2 | Linux | kill 进程标识PID |
进程操作接口
获取PID
进程标识pid:进程身份证号
No. | 函数 | 接口 |
---|---|---|
1 | pid_t getpid() |
获取当前进程ID |
2 | pid_t getppid() |
获取当前进程父进程ID |
- 示例
#include
#include
int main(){
printf("PID:%dPPID:%d\n",getpid(),getppid());
}
查看进程的PID和PPID
ps -o pid,ppid,cmd,s
如何创建进程
1 分叉函数pid_t fork()
- 返回值
No. | 返回值 | 含义 |
---|---|---|
1 | -1 |
失败 |
2 | 0 |
子进程逻辑控制流 |
3 | 其他(子进程PID) | 父进程逻辑控制流 |
-
特点
- 调用一次,返回两次
- 相同但是独立的地址空间
- 并发执行
- 共享文件
示例
调用一次,返回两次
#include
#include
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
pid_t pid = fork();
fork();
if(pid == 0){// child
printf("this is child\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
}else{
printf("this is father\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
}
for(;;);
}
相同但是独立的地址空间&并发执行
#include
#include
int i = 100;
int main(){
int j=100;
pid_t pid = fork();
if(pid == 0){// child
int k;
for(k=0;k<10000;k++)
printf("this is childi%d\t j%d\n",++i,++j);
}else{
int k;
for(k=0;k<10000;k++)
printf("this is fatheri%d\t j%d\n",--i,--j);
}
}
共享文件
#include
#include
int i = 100;
int main(){
int j=100;
FILE* fd = fopen("./test","w+");
pid_t pid = fork();
if(pid == 0){// child
int k;
for(k=0;k<10000;k++)
fprintf(fd,"this is childi%d\t j%d\n",++i,++j);
}else{
int k;
for(k=0;k<10000;k++)
fprintf(fd,"this is fatheri%d\t j%d\n",--i,--j);
}
}
- 本质
复制+分叉
No. | 概念 | 状态 | 硬件 | 特点 |
---|---|---|---|---|
1 | 并发(concurrency) | 两个或者多个进程在同时存在 | 单核 | 进程指令同时或者交错执行。 |
2 | 并行(parallellism) | 两个或者多个进程在同时执行 | 多核 | 一种特殊的并发 |
2 执行函数exec()
- 分类
No. | 分类 | 函数 |
---|---|---|
1 | 字符串数组参数 | execv() 、execvp() 、execve() |
2 | 可变参数 | execle() 、execlp() 、execl() |
-
exec
函数组名字规律
No. | 字符 | 含义 |
---|---|---|
1 | v |
第二个参数是数组 |
2 | l |
第二个参数之后是变参 |
3 | p |
第一个参数是文件名 |
4 | e |
最后一个参数是环境变量 |
- 返回值
No. | 返回值 | 含义 |
---|---|---|
1 | -1 |
失败 |
2 | 不返回 | 成功 |
- 特点
- 一次调用,失败返回
- 改朝换代,取而代之
- PID不变
- 地址空间内容变化
- 示例
#include
#include
#include
extern char ** environ;
int main(int argc,char** argv){
printf("%s,PID %d\n",argv[0],getpid());
//execl("/bin/ps","ps","-a","-o","pid,ppid,cmd",0);
//execlp("ps","ps","-a","-o","pid,ppid,cmd",0);
//execle("/bin/ps","ps","-a","-o","pid,ppid,cmd",0,environ);
char* args[] = {"ps","-a","-o","pid,ppid,cmd",0};
//execv("/bin/ps",args);
execve("/bin/ps",args,environ);
//execvp("ps",args);
printf("%s,PID %d\n",argv[0],getpid());
}
- 本质
覆盖程序
3 系统函数int system(Shell字符串)
- 返回值
No. | 返回值 | 含义 |
---|---|---|
1 | -1 |
失败 |
2 | 127 |
无法启动shell来运行 |
3 | 其他 | 命令退出码 |
- 特点
一次调用,一次返回 - 示例
#include
#include
#include
int main(int argc,char** argv){
printf("PID:%d\n",getpid());
system("sleep 3&");
printf("PID:%d\n",getpid());
}
- 本质
shell执行命令/程序
Linux系统可以创建多少个进程?使用
ulimit -a
可以查看到。可以通过ulimit -u 进程数
修改。
更详细的设置可以在/etc/security/limits.conf
修改。
如何结束进程
No. | 方式 | 说明 |
---|---|---|
1 | main 函数退出 |
只能用在main 函数内 |
2 | 调用exit() 函数 |
一般用在main 函数以外的函数 |
3 | 调用_exit() 函数 |
一般用来结束子进程 |
4 | 调用abort() 函数 |
一般用来异常退出 |
5 | 信号终止 | 终止其他进程 |
- 示例
return
退出
#include
#include
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
return 100;
}
exit()
/abort()
退出
#include
#include
#include
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
//exit(EXIT_FAILURE);
abort();
}
如何停止进程
休眠
int sleep(unsigned int secs)
- 参数
secs
指定休眠的秒数,-1
表示永久休眠 - 返回值
未休眠的秒数 - 特性
如果没有信号中断,休眠指定秒数返回0
,否则马上返回未休眠的秒数。 - 示例:电子时钟
#include
#include
#include
#include
int main(){
int i = 0;
for(;;){
time_t t;
time(&t);
struct tm* ptm = gmtime(&t);
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
strftime(buf,BUFSIZ,"%P %T",ptm);
printf("\r%s",buf);
fflush(stdout);
sleep(1);
}
}
暂停
int pause()
- 返回值
总是-1
- 特性
- 如果程序没有处理信号,直接中断,执行默认信号处理,程序后续代码不再执行。
- 如果程序存在信号处理,执行信号处理后,执行后续代码。
- 等待信号
No. | 快捷键 | 信号 | 说明 |
---|---|---|---|
1 | Ctrl+C |
SIGINT |
中断 |
2 | Ctrl+Z |
SIGTSTP |
终端的停止信号 |
- 示例
#include
#include
#include
void test(int sig){
printf("revc a signal%d",sig);
}
int main(){
signal(SIGINT,test);
printf("before pause\n");
pause();
printf("after pause\n");
}
等待
pid_t wait(int* status)
:等价pid_t waitpid(-1,stauts,0)
pid_t waitpid(pid_t pid,int * status,int options)
参数
No. | 参数 | 含义 | 说明 |
---|---|---|---|
1 | pid |
等待的进程 | <-1 :等待进程组为pid的所有进程;-1 : 等待任何子进程;0 :等待同组的进程;>0 :进程为pid 的子进程 |
2 | status |
子进程结束状态 | 判断正常结束:使用WIFEXITED(status) ;判断异常结束使用WIFSIGNALED(status) ;判断暂停使用WIFSTOPPED(status) |
3 | options |
选项 | WNOHANG 若子进程没有结束,返回0,不予以等待;若子进程结束,返回该子进程的ID。WUNTRACED 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。 |
- 返回值
No. | 返回值 | 含义 |
---|---|---|
1 | -1 |
失败 |
2 | 其他 | 等待的PID |
- 正常结束:
WIFEXITED(status)
No. | 参数 | 含义 |
---|---|---|
1 | 非0 |
正常结束子进程 |
2 | 0 |
非正常结束子进程 |
WEXITSTATUS(status)
取得子进程exit()
返回的结束代码
一般会先用WIFEXITED
来判断是否正常结束才能使用此宏
- 异常结束:WIFSIGNALED(status)
No. | 参数 | 含义 |
---|---|---|
1 | 非0 |
异常结束子进程 |
2 | 0 |
非异常结束子进程 |
WTERMSIG(status)
取得子进程因信号而中止的信号代码
一般会先用WIFSIGNALED
来判断后才使用此宏
- 暂停:
WIFSTOPPED(status)
No. | 参数 | 含义 |
---|---|---|
1 | 非0 |
暂停结束子进程 |
2 | 0 |
非暂停结束子进程 |
WSTOPSIG(status)
取得引发子进程暂停的信号代码
一般会先用WIFSTOPPED
来判断后才使用此宏。
- 示例
父进程等待子进程退出
#include
#include
#include
#include
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
pid_t pid = fork();
if(pid == 0){// child
sleep(2);
printf("this is child\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
//exit(0);
}else{
printf("this is father\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
printf("pid:%d exit\n",waitpid(pid,NULL,0));
}
}
更加安全的方式
#include
#include
#include
#include
void handler(int sig){
int status;
pid_t cpid = wait(&status);
if(WIFEXITED(status)){
printf("child exit by %d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status)){
printf("child exit by signal %d\n",WTERMSIG(status));
}
printf("child %d exit\n",cpid);
}
int main(){
signal(SIGCHLD,handler);
printf("PID:%d,PPID:%d\n",getpid(),getppid());
pid_t pid = fork();
if(pid == 0){// child
sleep(2);
printf("this is child\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
//exit(0);
}else{
printf("this is father\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
printf("leave:%d\n",sleep(5));
//exit(200);
}
for(;;);
}
如果不关心退出的情况。
#include
#include
#include
void handler(int sig){
pid_t cpid = wait(NULL);
printf("child %d exit",cpid);
}
int main(){
signal(SIGCHLD,handler);
printf("PID:%d,PPID:%d\n",getpid(),getppid());
pid_t pid = fork();
if(pid == 0){// child
sleep(2);
printf("this is child\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
}else{
printf("this is father\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
}
}
进程的终老、他杀与自杀
特殊进程
No. | 概念 | 出现条件 | 导致结果 | 是否有害 |
---|---|---|---|---|
1 | 孤儿进程 | 父进程先于子进程退出 | init 进程作为新的父进程 |
无害 |
2 | 僵尸进程 | 子进程退出,父进程没有获取子进程的状态信息 | 调用wait 或waitpid |
有害,避免出现僵尸进程 |
- 示例
#include
#include
#include
void handle(int sig){
//wait(NULL);
printf("this is child exit %d",sig);
}
int main(){
signal(SIGCHLD,handle);
printf("PID:%d,PPID:%d\n",getpid(),getppid());
pid_t pid = fork();
if(pid == 0){// child
printf("this is child\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
}else{
printf("this is father\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
for(;;);
}
}