提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
对于C语言中涉及的进程、线程和进程之间的通信,将会以文章的形式对其进行讲解,也可以充当复习的笔记使用。
提示:以下是本篇文章正文内容,下面案例可供参考
一、进程是什么?
程序:存放在磁盘上的指令和数据的有序集合(文件),是静态的。
进程:执行一个程序所分配资源的总称,进程是程序的一次执行过程,是动态的,生命周期:包括创建、调度、执行和消亡。
二、查看进程信息
ps // 查看系统进程快照
top // 实时查看系统信息 tip: shift + > 后翻页 shift + < 前翻页
top -p PID // 查看PID的进程
nice [-n Ni值] 命令 // 按用户制定的优先级运行进程-
// -NI 范围是-20~19,数值越大,优先级越低。
renice [优先级] PID // renice 改变正在运行进程的优先级
1.创建子进程
1 . 子进程为由另外一个进程(对应称为父进程)所创建的进程。
代码如下(示例):创建fork_t.c 文件。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork(); // fork 主打复制
// 让父子进程执行不同的代码----执行的先后顺序,没有规律,由操作系统进行调度
// 通过fork的返回值区分父进程和子进程
// 子进程只执行fork之后的代码
if (pid > 0)
{
printf("This is a father process\n");
}else if(pid == 0)
{
printf("This is a son process!\n");
} else if(pid < 0)
{
perror("fork");
return 0;
}
printf("pid = %d\n", pid); // pid=403, pid = 0
return 0;
}
2.父子进程之间的关系
- 子进程继承了父进程的内容
- 父子进程有独立的地址空间,互不影响。
- 若父进程先结束
3.1 子进程变为孤儿进程,被init进程收养。
3.2 子进程变为后台进程。 - 若子进程先结束
4.1 父进程如果没有及时回收,子进程变为僵尸进程。
ps -elf|grep fork_t // 查看fork_t 的PID 和PPID
kill -9 490 // 杀死父进程ID 490
3. 子进程的进阶
3.1 要达到一个父进程产生多个子进程的时候,例如下图。
代码如下:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t pid;
for (size_t i = 0; i < 5; i++)
{
pid = fork();
if (pid < 0)
{
perror("fork");
return 0;
} else if(pid == 0)
{
printf("This is child process!\n");
sleep(5);
break; // 不让子进程再次复制
} else {
printf("This is father process!\n");
sleep(5);
}
sleep(100); // 避免子进程直接变为僵尸进程。
}
return 0;
}
3.2 同理要产生孙进程,那么让父进程直接跳出当前for循环,不再进行复制即可。
4. 进程的退出
4.1 创建文件exit_t.c,展示exit在main函数中调用后的作用。
exit (正常结束进程)
相关函数 _exit, atexit, on_exit
头文件 #include <stdlib.h>
定义函数 void exit(int status);
函数说明 exit()用来正常终结目前进程的执行, 并把参数 status 返回给父进程, 而进程所有的缓冲区数据会自
动写回并关闭未关闭的文件.
返回值
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
printf("hello world"); // 不加换行符,不会刷新缓冲区,也就是无法在屏幕呈现出这句话。
exit(0); // 充当刷新缓冲区的作用。
printf("after exit"); // 这句话再执行的时候不会被打印。
return 0; // 只有在main 函数中,才会隐式调用了exit函数
}
补充: exit函数的作用是结束当前的进程并将status返回,exit结束进程时会刷新(流)缓冲区。
注意: return 和exit的区别
main函数结束时会隐式调用exit函数,普通函数中return是返回上一级。
5. 进程的回收
子进程是由父进程创造出来的,当子进程结束后,父进程没有及时回收子进程的话,子进程就会变为僵尸进程,占用系统的资源,这节主要将怎么对子进程进行回收。
wait (等待子进程中断或结束)
相关函数 waitpid, fork
头文件 #include <sys/types.h>
#include <sys/wait.h>
定义函数 pid_t wait (int * status);
函数说明 wait()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用 wait()时子进程已经
结束, 则 wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数 status 返回, 而子进程的进程识
别码也会一快返回. 如果不在意结束状态值, 则 参数 status 可以设成 NULL. 子进程的结束状态值请参考
waitpid().
返回值 如果执行成功则返回子进程识别码(PID), 如果有错误发生则返回-1. 失败原因存于 errno 中.
代码演示:
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid_t rpid;
pid = fork();
int status;
if (pid < 0)
{
perror("fork");
return 0; // pid < 0 直接退出
}else if(pid > 0){ // pid > 0 父进程
rpid = wait(&status);
printf("Get child status=%x\n", WEXITSTATUS(status)); // 返回自定义的返回值
} else if(pid == 0) { // pid == 0 是子进程
sleep(1);
printf("child will exit!\n");
exit(2);
}
return 0;
}
运行 gcc -o wait_t wait_t.c 后,发现没有僵尸进程了,返回了自定义的返回值。
拓展知识: waitpid函数- -> 可以查看子进程的结束状态值
waitpid (等待子进程中断或结束)
相关函数 wait, fork
头文件 #include <sys/types.h>
#include <sys/wait.h>
定义函数 pid_t waitpid(pid_t pid, int * status, int options);
函数说明 waitpid()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用 wait()时子进程
已经结束, 则 wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数 status 返回, 而子进程的进
程识别码也会一快返回. 如果不在意结束状态值, 则参数 status 可以设成 NULL. 参数 pid 为欲等待的子进程识
别码, 其他数值意义如下:
pid<-1 等待进程组识别码为 pid 绝对值的任何子进程.
pid=-1 等待任何子进程, 相当于 wait().
pid=0 等待进程组识别码与目前进程相同的任何子进程.
pid>0 等待任何子进程识别码为 pid 的子进程.
参数 option 可以为 0 或下面的 OR 组合
WNOHANG 如果没有任何已经结束的子进程则马上返回, 不予以等待.
WUNTRACED 如果子进程进入暂停执行情况则马上返回, 但结束状态不予以理会. 子进
程的结束状态返回后存于 status, 底下有几个宏可判别结束情况
WIFEXITED(status) 如果子进程正常结束则为非 0 值.
WEXITSTATUS(status) 取得子进程 exit()返回的结束代码, 一般会先用 WIFEXITED 来判断是
否正常结束才能使用此宏.
WIFSIGNALED(status) 如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status) 取得子进程因信号而中止的信号代码, 一般会先用 WIFSIGNALED 来判断
后才使用此宏.
WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为真. 一般只有使用 WUNTRACED
时才会有此情况.
WSTOPSIG(status) 取得引发子进程暂停的信号代码, 一般会先用 WIFSTOPPED 来判断后才
使用此宏.
返回值 如果执行成功则返回子进程识别码(PID), 如果有错误发生则返回-1. 失败原因存于 errno 中.
代码如下:
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid_t rpid;
pid = fork();
int status;
if (pid < 0)
{
perror("fork");
return 0; // pid < 0 直接退出
}else if(pid > 0){ // pid > 0 父进程
//rpid = wait(&status);
rpid = waitpid(-1, &status, 0); // waitpid(任意值=-1, &status, 堵塞的=0)
printf("Get child status=%x\n", WEXITSTATUS(status)); // 返回自定义的返回值
} else if(pid == 0) { // pid == 0 是子进程
sleep(1);
printf("child will exit!\n");
exit(2);
}
return 0;
}
总结
本篇文章对于进程的概念、进程常用命令、子进程的创建、进阶、退出、回收进行了阐述。代码中所涉及的头文件,在LInux终端下,可以采用以下命令进行查看和阅读详细介绍。
1. man -f fork
2. man -f wait
3. man -2 wait // 基于2的命令后,选择第二个选项进行详细查看,因为命令介绍手册有好几本。
4. ps elf|grep wait_t // 一般查看wait_t.c 代码执行完后,子进程的情况,看看子进程中是否仍有僵尸进程存在。
上面的命令在LInux系统下,可以多尝试尝试,了解所涉及函数的用法。