一、进程概念
1.概念
程序
存放在磁盘上的指令和数据的有序集合(文件)
静态的
进程
执行一个程序所分配的资源的总称
进程是程序的一次执行过程
动态的,包括创建、调度、执行和消亡
进程比程序多了堆、栈和进程控制模块。
程序位于硬盘ROM中,进程位于内存RAM中。
BSS段(Block Started by Symbol):用来存放程序中未初始化的全局变量和静态变量的一块内存区域。
数据段:数据段通常是指用来存放程序中已初始化的全局变量的一块内存区域。
代码段:代码段通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
进程控制块(pcb):
进程标识PID 、进程用户、进程状态、优先级、文件描述符表
2.进程类型
交互进程:在shell下启动。以在前台运行,也可以在后台运行
批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
守护进程:和终端无关,一直在后台运行
3.进程状态
运行态:进程正在运行,或者准备运行
等待态:进程在等待一个事件的发生或某种系统资源
可中断
不可中断
cpu没有这么多的资源同时运行这么多的进程,进程需要轮流使用cpu,这也就需要进程进入等待状态
停止态:进程被中止,收到信号后可继续运行
死亡态:已终止的进程,但pcb没有被释放
二、进程常用命令
1.查看进程信息
ps 查看系统进程快照,只显示当前终端运行的进程。
ps -e //查看linux下的所有进程
ps -elf
ps -elf|grep name
top 查看进程动态信息
shift+> 后翻页
shift+< 前翻页
top -p PID 查看某个进程
/proc 查看进程详细信息
表头 | 含义 |
F | 进程标志,说明进程的权限,常见的标志有两个: 1:进程可以被复制,但是不能被执行; 4:进程使用超级用户权限; |
S | 进程状态。进程状态。常见的状态有以下几种:
|
UID | 运行此进程的用户的 ID; |
PID | 进程的 ID; |
PPID | 父进程的 ID; |
C | 该进程的 CPU 使用率,单位是百分比; |
PRI | 进程的优先级,数值越小,该进程的优先级越高,越早被 CPU 执行; |
NI | 进程的优先级,数值越小,该进程越早被执行; |
ADDR | 该进程在内存的哪个位置; |
SZ | 该进程占用多大内存; |
WCHAN | 该进程是否运行。"-"代表正在运行; |
TTY | 该进程由哪个终端产生; |
TIME | 该进程占用 CPU 的运算时间,注意不是系统时间; |
CMD | 产生此进程的命令名; |
2.进程相关命令
nice 按用户指定的优先级运行进程
nice [-n NI值] 命令
NI 范围是 -20~19。数值越大优先级越低
普通用户调整 NI 值的范围是 0~19,而且只能调整自己的进程。
普通用户只能调高 NI 值,而不能降低。如原本 NI 值为 0,则只能调整为大于 0。
只有 root 用户才能设定进程 NI 值为负值,而且可以调整任何用户的进程。
renice 改变正在运行进程的优先级
renice [优先级] PID
jobs 查看后台进程
程序运行中,按ctrl+z,将程序放到后台并停止,用命令jobs 查看
bg 将挂起的进程在后台运行
fg 把后台运行的进程放到前台运行
ctrl+z 把运行的前台进程转为后台并停止
./test & 把test程序后台运行
三、创建子进程
1.概念
2.子进程创建 – fork
#include <unistd.h>
pid_t fork(void);
创建新的进程,失败时返回-1
成功时父进程返回子进程的进程号(>0),子进程返回0
通过fork的返回值区分父进程和子进程
代码演示:
#include <stdio.h>
#include <unistd.h>
int main(int argc,char **argv){
pid_t pid;
printf("before fork\n");
pid = fork(); //创建进程
if(pid>0){ //父进程
printf("This is father process\n");
printf("pid=%d\n",(int)pid); //(int)pid 可替换为 getpid(),获取当前进程的pid
printf("father after fork\n");
while(1){
sleep(1);
printf("father sleep\n");
}
}else if(pid==0){ //子进程
printf("This is child process\n");
printf("pid=%d\n",(int)pid);
printf("child after fork\n");
while(1){
sleep(1);
printf("child sleep\n");
}
}else if(pid<0){ //pid<0,打印错误信息
perror("fork");
return 0;
}
// printf("pid=%d\n",(int)pid);
// printf("after fork\n");
}
注意:
1)子进程只执行fork之后的代码;
2)父子进程执行顺序是操作系统决定的;
3)子进程继承了父进程的内容;
4)父子进程有独立的地址空间,互不影响;
5)若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程;
若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程。
由父进程生成5个子进程
代码演示:
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid;
int i;
for(i=0;i<5;i++){
pid = fork(); //子进程运行fork之后的代码,但位于循环体内,循环执行
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
printf("child process\n");
sleep(5);
break; //子进程跳出循环,不生成孙进程
}else{
printf("Father process\n");
sleep(5); //breaK //若需要单个父进程生成单个子进程,则父进程跳出循环
}
}
sleep(100);
}
五、进程的退出
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
void _Exit(int status); //_exit和_Exit用法一致
功能:
结束当前的进程并将status返回
exit结束进程时会刷新(流)缓冲区,_exit则不会
代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char**argv){
printf("hello world"); //字符串位于缓冲区
_exit(0); //直接结束进程,未刷新缓冲区,因此“hello world”不打印
printf("after exit");
return 0;
}
return 和exit的区别
main函数结束时会隐式地调用exit函数,普通函数return是返回上一级。
六、进程的回收
子进程结束时由父进程回收、孤儿进程由init进程回收
若没有及时回收会出现僵尸进程
1.wait
#include <sys/wait.h>
#include <sys/types.h>
pid_t wait(int *status);
成功时返回回收的子进程的进程号;失败时返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,哪个先结束就先回收
status 指定保存子进程返回值和结束方式的地址
status为NULL表示直接释放子进程PCB,不接收返回值
进程返回值和结束方式
子进程通过exit/ exit / return 返回某个值(0-255)
父进程调用wait(&status)回收
WIFEXITED(status) 判断子进程是否正常结束
WEXITSTATUS(status) 获取子进程返回值
WIFSIGNALED(status) 判断子进程是否被信号结束
WTERMSIG(status) 获取结束子进程的信号类型
代码演示:
//省略...
int status;
pid_t pid;
if((pid = fork())<0){
perror("fork"); exit(-1);
}
else if (pid == 0){
sleep(1);
exit(2); //退出
}
else {
wait(&status); //等待子进程结束,结束后的返回值和结束方式保存在&status中
printf("%x\n", status);
}
2.waitpid
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int option);
成功时返回回收的子进程的pid或0;失败时返回EOF
pid可用于指定回收哪个子进程或任意子进程
status指定用于保存子进程返回值和结束方式的地址
option指定回收方式,0(阻塞,等待进程结束再回收)
或 WNOHANG(若进程还未结束,则返回0;若>0,表示进程结束,回收成功)
示例:
waitpid(pid, &status, 0);
waitpid(pid, &status, WNOHANG);
waitpid(-1, &status, 0); //-1表示当前进程的任一子进程
waitpid(-1, &status, WNOHANG);
代码演示:
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv){
pid_t pid;
pid_t rpid;
pid = fork();
int status;
if(pid<0){
perror("fork");
return 0;
}
else if(pid == 0){
sleep(10);
printf("child will exit\n");
exit(2);
}else if(pid >0){
//rpid = wait(&status);
sleep(20);
waitpid(-1,&status,WNOHANG);
printf("Get child status=%x\n",WEXITSTATUS(status));
}
while(1){
sleep(1);
}
}
七、作业
实现一个进程链,父进程->子进程->孙进程->重孙进程->重重孙进程
#include<stdio.h>
#include<unistd.h>
int main(){
pid_t pid;
int i;
for(i = 0;i < 5;i++){
pid = fork();
if(pid < 0){
perror("fork");
return 0;
}else if(pid == 0){
printf("child process\n");
sleep(5);
}else{
printf("father process\n");
sleep(5);
break;
}
}
sleep(100);
return 0;
}