1.进程的概念
程序
存放在磁盘上的指令和数据的有序集合(文件)
静态的
进程
执行一个程序所分配的资源的总称
进程是程序的一次执行过程
动态的,包括创建、调度、执行和消亡
进程包含的内容
BSS段:存放程序中未初始化的全局变量
数据段:存放程序中已初始化的全局变量的一块内存区域。
代码段:代码段通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
进程控制块(pcb):包括 进程标识PID 进程用户 进程状态、优先级 文件描述符表
进程类型
交互进程:在shell下启动。以在前台运行,也可以在后台运行
批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
守护进程:和终端无关,一直在后台运行
进程状态
运行态、等待态、停止态、死亡态:
可中断等待态:
进程正在等待某个事件或资源,但可以被信号中断。
在这种状态下,如果进程收到了相关的信号或者所需资源变得可用,它就会从等待状态进入可运行状态,并加入到运行队列中等待被调度。此外,外部中断(如信号)也可以唤醒这个进程。
不可中断等待态:
进程同样在等待某个事件或资源,但与可中断等待态不同,它不能被信号中断。
进程通常因为硬件环境不能满足其需求(如等待特定的系统资源)而进入此状态。在此状态下,进程在任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等。只有当它所等待的资源准备好后,它才会被唤醒。
查看进程信息
ps 查看系统进程快照
ps 命令详细参数:
-e:显示所有进程
-l:长格式显示更加详细的信息
-f 全部列出,通常和其他选项联用
表头 | 含义 |
F | 进程标志,说明进程的权限,常见的标志有两个:
|
S | 进程状态。进程状态。常见的状态有以下几种:
|
UID | 运行此进程的用户的 ID; |
PID | 进程的 ID; |
PPID | 父进程的 ID; |
C | 该进程的 CPU 使用率,单位是百分比; |
PRI | 进程的优先级,数值越小,该进程的优先级越高,越早被 CPU 执行; |
NI | 进程的优先级,数值越小,该进程越早被执行; |
ADDR | 该进程在内存的哪个位置; |
SZ | 该进程占用多大内存; |
WCHAN | 该进程是否运行。"-"代表正在运行; |
TTY | 该进程由哪个终端产生; |
TIME | 该进程占用 CPU 的运算时间,注意不是系统时间; |
CMD | 产生此进程的命令名; |
top 查看进程动态信息
shift +> 后翻页
shift +< 前翻页
top -p PID 查看某个进程
/proc 查看进程详细信息
前后台进程切换
jobs 查看后台进程
bg 将挂起的进程在后台运行
fg 把后台运行的进程放到前台运行
ctrl+z 把运行的前台进程转为后台并停止。
./test & 把test程序后台运行
改变进程优先级
nice 按用户指定的优先级运行进程
nice [-n NI值] 命令
NI 范围是 -20~19。
数值越大优先级越低
普通用户调整 NI 值的范围是 0~19,而且只能调整自己的进程。
普通用户只能调高 NI 值,而不能降低。如原本 NI 值为 0,则只能调整为大于 0。
只有 root 用户才能设定进程 NI 值为负值,而且可以调整任何用户的进程。
renice 改变正在运行进程的优先级
renice [优先级] PID
2.父进程子进程
1.子进程
子进程概念
子进程为由另外一个进程(对应称之为父进程)所创建的进程
#include <unistd.h>
pid_t fork(void);
创建新的进程,失败时返回-1
成功时父进程返回子进程的进程号,子进程返回0
通过fork的返回值区分父进程和子进程
#include<stdio.h>
#include<aio.h>
#include <unistd.h>
int main(int argc ,char** argv){
pid_t pid;
printf("before fork\n");
pid = fork();
printf("after fork\n");
printf("%d\n",pid);
if (pid<0){
perror("fork");
return 0;
}else if(pid==0){
printf("child process:my pid is %d\n",getpid());
}
else{
printf("parent process:my pid is %d\n",getpid());
}
return 0;
}
结果:
before fork
after fork
289459
parent process:my pid is 289458
after fork
0
child process:my pid is 289459
2.父子进程
子进程继承了父进程的内容
父子进程有独立的地址空间,互不影响
若父进程先结束
子进程成为孤儿进程,被init进程收养
子进程变成后台进程
这个init进程是系统启动时的第一个进程,并且它的PID(进程ID)总是1。init进程(或其现代等价物)负责处理这些孤儿进程,确保它们得到适当的清理和终止。
然而,需要注意的是,仅仅因为一个进程成为了孤儿进程,并不意味着它立即停止执行。它会继续运行,直到它自己终止,或者收到一个终止信号。init进程(或systemd)通常会确保这些进程最终会被正确地处理,但具体的行为可能取决于系统的配置和这些进程的性质。
若子进程先结束
父进程如果没有及时回收,子进程变成僵尸进程
僵尸进程是已经终止运行但其父进程尚未收集其终止状态信息的进程。它们在进程表中占用一个条目,但不会执行任何代码或消耗除进程表条目外的系统资源。尽管如此,大量的僵尸进程可能会耗尽可用的进程ID,从而阻止新进程的创建。
3.练习
为一个父进程创建五个子进程(需要注意的是在循环的过程中子进程也会生成新的子进程 )
3.进程退出
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
void _exit(int status);
void _Exit(int status);
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf(“this process will exit”);
exit(0);
printf(“never be displayed”);
}
//结果
this process will be exit
结束当前的进程并将status返回
exit结束进程时会刷新(流)缓冲区
return 和exit的区别
main函数结束时会隐式地调用exit函数,普通函数return是返回上一级。
4.进程回收
1.wait
子进程结束时由父进程回收
孤儿进程由init进程回收
若没有及时回收会出现僵尸进程
#include <sys/wait.h>
pid_t wait(int *status);
成功时返回回收的子进程的进程号;
失败时返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,哪个先结束就先回收
status 指定保存子进程返回值和结束方式的地址
status为NULL表示直接释放子进程PCB,不接收返回值
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv) {
pid_t pid;
int status; // 将状态变量的声明移到更合适的位置(可选)
pid = fork(); // 创建子进程
if (pid < 0) { // 如果fork失败
perror("fork"); // 打印错误原因
return 1; // 退出程序并返回错误码(通常非0表示错误)
} else if (pid == 0) { // 子进程代码块
sleep(1); // 子进程休眠1秒(可选,仅用于演示目的)
printf("child is exiting\n"); // 更准确的消息文本(但可能不会立即显示)
fflush(stdout); // 刷新标准输出缓冲区以确保消息被打印(可选)
exit(2); // 子进程退出并返回状态码2
} else { // 父进程代码块
pid_t rpid = wait(&status); // 等待子进程并获取其状态信息
printf("get child exit status=%d\n", WEXITSTATUS(status)); // 使用宏来正确解析并打印子进程的退出状态码
}
return 0; // 程序正常结束并返回0(通常表示成功)
}
2.waitpid
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int option);
成功时返回回收的子进程的pid或0;失败时返回EOF
pid可用于指定回收哪个子进程或任意子进程
status指定用于保存子进程返回值和结束方式的地址
option指定回收方式,0 或 WNOHANG
参数
pid
pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
options
options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用
WNOHANG :若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0
WUNTRACED: 返回终止子进程信息和因信号停止的子进程信息
wait(wait_stat) 等价于waitpid(-1,wait_stat,0)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.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(1);
printf("chilid will exit\n");
exit(2);
}else{
waitpid(pid,&status,0);
printf("get child status:%d\n", WEXITSTATUS(status));
}
return 0;
}