进程概念
程序
存放在磁盘上的指令和数据的有序集合(文件)
静态的
进程
执行一个程序所分配的资源的总称
动态的
进程和程序内容区别
进程包含的内容:
BSS段:存放程序中未初始化的全局变量
数据段:已初始化的全局变量
代码段:程序执行代码
堆(heap):malloc等函数分配内存
栈(stack):局部变量,函数参数,函数的返回值
进程控制块(pcb):PID, 进程优先级,文件描述符表
进程类型
交互进程、批处理进程、守护进程。
交互进程:在shell下启动。以在前台运行,也可以在后台运行
批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
守护进程:和终端无关,一直在后台运行
进程状态
运行态、等待态、停止态、死亡态:
运行态:进程正在运行,或者准备运行
等待态:进程在等待一个事件的发生或某种系统资源 可中断 不可中断
停止态:进程被中止,收到信号后可继续运行
死亡态:已终止的进程,但pcb没有被释放
查看进程信息
ps 查看系统进程快照
top 查看进程动态信息
/proc 查看进程详细信息
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 查看某个进程
改变进程优先级
nice 按用户指定的优先级运行进程
nice [-n NI值] 命令
NI 范围是 -20~19。数值越大优先级越低
普通用户调整 NI 值的范围是 0~19,而且只能调整自己的进程。
普通用户只能调高 NI 值,而不能降低。如原本 NI 值为 0,则只能调整为大于 0。
只有 root 用户才能设定进程 NI 值为负值,而且可以调整任何用户的进程。
renice 改变正在运行进程的优先级
renice [优先级] PID
jobs 查看后台进程
bg 将挂起的进程在后台运行
fg 把后台运行的进程放到前台运行
ctrl+z 把运行的前台进程转为后台并停止。
./test & 把test程序后台运行
创建子进程:
子进程概念:
子进程为由另外一个进程(对应称之为父进程)所创建的进程
#include <unistd.h>
pid_t fork(void);
创建新的进程,失败时返回-1
成功时父进程返回子进程的进程号,子进程返回0
通过fork的返回值区分父进程和子进程
要点:1 子进程只执行fork之后的代码
2 父子进程执行顺序是操作系统决定的。
父子进程
子进程继承了父进程的内容
父子进程有独立的地址空间,互不影响
若父进程先结束
子进程成为孤儿进程,被init进程收养
子进程变成后台进程
若子进程先结束
父进程如果没有及时回收,子进程变成僵尸进程
#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);
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){
perror("fork");
return 0;
}
// printf("pid=%d\n",(int)pid);
// printf("after fork\n");
}
root@05efffc6e749:~/20230725/process/code# ps -elf|grep fork_t
0 S root 10382 9489 0 80 0 - 55337 hrtime 17:54 pts/6 00:00:00 /usr/bin/qemu-aarch64-static ./fork_t
1 S root 10385 10382 0 80 0 - 55337 hrtime 17:54 pts/6 00:00:00 /usr/bin/qemu-aarch64-static ./fork_t
0 S root 10389 4590 0 80 0 - 56902 pipe_w 17:55 pts/5 00:00:00 /usr/bin/qemu-aarch64-static /bin/grep --color=auto fork_t
root@05efffc6e749:~/20230725/process/code# kill -9 10382
root@05efffc6e749:~/20230725/process/code# kill 10389
bash: kill: (10389) - 没有那个进程
root@05efffc6e749:~/20230725/process/code# kill -9 10385
#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);
break;
}else{
printf("Father process\n");
sleep(5);
}
}
sleep(100);
}
执行效果
进程的退出
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
void _exit(int status);
结束当前的进程并将status返回
exit结束进程时会刷新(流)缓冲区
return 和exit的区别
main函数结束时会隐式地调用exit函数,普通函数return是返回上一级。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char**argv){
printf("hello world");
_exit(0);
printf("after exit");
return 0;
}
在C语言中,`exit()`函数是一个库函数,用于终止程序的执行,并返回一个状态码给操作系统。它的原型定义在`<stdlib.h>`头文件中。
其中,`status`是要返回给操作系统的状态码。通常,0表示成功,非零值表示错误或异常情况。
以下是几种常见的`exit()`函数的用法:
1. 正常退出程序:
```c
#include <stdlib.h>
int main() {
// 程序执行的代码
// ...
// 正常退出程序,返回状态码为0
exit(0);
}
```
2. 异常退出程序:
```c
#include <stdlib.h>
int main() {
// 程序执行的代码
// ...
// 发生异常,退出程序并返回状态码为1
exit(1);
}
```
3. 根据条件退出程序:
```c
#include <stdlib.h>
int main() {
int result;
// 执行某些操作并根据结果判断是否退出程序
// ...
if (result == 0) {
// 结果为0,正常退出程序,返回状态码为0
exit(0);
} else {
// 结果不为0,退出程序并返回状态码为1
exit(1);
}
}
```
需要注意的是,`exit()`函数会立即终止程序的执行,不会执行后续的代码,包括`main()`函数中的`return`语句。因此,在使用`exit()`函数时应当确保程序已经完成了需要执行的操作,不会导致未处理的资源泄露或异常情况。
进程的回收
子进程结束时由父进程回收
孤儿进程由init进程回收
若没有及时回收会出现僵尸进程
#include <unistd.h>
pid_t wait(int *status);
成功时返回回收的子进程的进程号;失败时返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,哪个先结束就先回收
status 指定保存子进程返回值和结束方式的地址
status为NULL表示直接释放子进程PCB,不接收返回值
进程返回值和结束方式
#include <unistd.h>
pid_t waitpid(pid_t pid, int *status, int option);
子进程通过exit / _exit / return 返回某个值(0-255) 父进程调用wait(&status) 回收 WIFEXITED(status) 判断子进程是否正常结束
WEXITSTATUS(status) 获取子进程返回值
WIFSIGNALED(status) 判断子进程是否被信号结束
WTERMSIG(status)获取结束子进程的信号类型
进程回收 – 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 <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
参数说明:
pid
:要等待的子进程的进程ID,可以指定为以下几个值:< -1
:等待进程组ID为-pid
的任意子进程。-1
:等待任意子进程。0
:等待与调用进程同一进程组的任意子进程。> 0
:等待进程ID为pid
的子进程。
status
:指向一个整型变量的指针,用于存储子进程的终止状态。options
:可选参数,用于控制waitpid()
函数的行为。常用的选项有:WNOHANG
:如果指定的子进程还没有改变状态,则立即返回0,而不会阻塞等待。WUNTRACED
:如果子进程进入暂停状态,则立即返回。
waitpid()
函数的返回值是被等待的子进程的进程ID。如果调用失败,返回-1,并设置errno
为相应的错误码。
以下是一些waitpid()
函数的用法示例:
- 等待任意子进程的终止状态改变,并获取终止状态:
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程执行的代码
exit(EXIT_SUCCESS);
} else {
// 父进程执行的代码
int status;
pid_t child_pid = waitpid(pid, &status, 0);
if (child_pid == -1) {
perror("waitpid failed");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status)) {
printf("Child process %d exited normally with status %d\n", child_pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child process %d terminated by signal %d\n", child_pid, WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
printf("Child process %d stopped by signal %d\n", child_pid, WSTOPSIG(status));
}
}
return 0;
}
- 非阻塞等待子进程的终止状态改变:
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程执行的代码
exit(EXIT_SUCCESS);
} else {
// 父进程执行的代码
int status;
pid_t child_pid = waitpid(pid, &status, WNOHANG);
if (child_pid == -1) {
perror("waitpid failed");
exit(EXIT_FAILURE);
}
if (child_pid == 0) {
printf("Child process is still running\n");
} else {
if (WIFEXITED(status)) {
printf("Child process %d exited normally with status %d\n", child_pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child process %d terminated by signal %d\n", child_pid, WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
printf("Child process %d stopped by signal %d\n", child_pid, WSTOPSIG(status));
}
}
}
return 0;
}
这些示例演示了waitpid()
函数的基本用法,你可以根据需要进行修改和扩展。需要注意的是,在使用waitpid()
函数时,应确保在调用fork()
函数之后,父进程和子进程使用不同的代码路径,以便分别处理不同的逻辑。