Linux的0、1和2号进程
整个linux系统全部的进程是一个树形结构。
0号进程(系统进程)是所有进程的祖先,它创建了1号和2号进程。
(相当于是我们世界的时间线)
1号进程(systemd)负责执行内核的初始化工作和进行系统配置。
2号进程(kthreadd)负责所有内核线程的调度和管理。
如何创建一个子进程
pid_t fork(void);
使用fork函数,使用fork函数创建的进程称为子进程,子进程和父进程共同执行fork函数及fork函数之后的代码。
可以实现父子进程运行不同的代码吗?
可以,因为fork函数虽然被调用一次,但是却返回两次值,一次是子类的,默认是0,一次是父类进程,默认是子类的进程号。
执行逻辑是,如果返回了值,就先执行后面的代码,执行完后,再接收第二个返回值,再次执行后面的代码。
依据这个逻辑,可以利用if -else实现两种代码功能,如果fork返回值为0就是子类时,执行一种情况,如果是非0时就是父类,执行另一种情况。
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
int bh=8;
string message="轻舟已过万重山。";
pid_t pid=fork();
if (pid>0)
{ // 父进程将执行这段代码。
sleep(1);
cout << "父:pid=" << pid << endl;
cout << "父:我是父亲" << bh << "号:" << message << endl;
}
else
{ // 子进程将执行这段代码。
bh=3; message="两岸猿声啼不住。";
cout << "子:pid=" << pid << endl;
cout << "子:我是儿子" << bh << "号:" << message << endl;
因为父进程比子进程慢了1s,所以是先返回子类,然后再返回父类。
父进程和子进程的进程号相同吗?
不同,通过es - pf|grep demo可以知道。
其中,上面是父进程,下面是子进程, 第3列是父进程编号,可知子进程的父进程31895和上面相同。但归根还是不同的进程。
子进程和父进程是共享变量吗?
是共享变量,但是子进程如果将变量进行了修改,那么就不是共享变量了,此时父进程并没有受到子进程改变变量影响。因为共享的变量只是虚拟地址相同,但是物理地址不同。
僵尸进程
父进程先关闭子进程后关闭
如果父进程比子进程先退出,子进程将被1号进程托管(这也是一种让程序在后台运行的方法)。在父进程后面加一个return 0,子进程while循环,父进程关闭后,子进程被1号进程托管。
子进程先关闭父进程后关闭
如果子进程比父进程先退出,而父进程没有处理子进程退出的信息,那么,子进程将成为僵尸进程。
由图可知,如果父进程先走了,那么子进程就会成孤儿,查看进程就会发现<defunct>,不可用。
使用top查看当前性能也可知道有一个僵尸进程
僵尸进程的危害
因为CPU会为每一个进程都分配数据结构,如果存在僵尸进程,那么就会导致资源不会被释放。
更重要的是,僵尸进程占用了进程号,如果在系统繁忙的时候,有可能会因为没有足够的进程号而不会创建新的进程。
如何避免僵尸进程
子进程退出的时候,内核会向父进程发头SIGCHLD信号,如果父进程用signal(SIGCHLD,SIG_IGN)通知内核,表示自己对子进程的退出不感兴趣,那么子进程退出后会立即释放数据结构。
Linux使用信号
对于单进程,使用信号很简单,给程序发送信号,让服务程序退出。
如何杀死多进程?
在多进程的服务程序中,如果子进程收到退出信号,子进程自行退出,如果父进程收到退出信号,则应该先向全部的子进程发送退出信号,然后自己再退出。
测试代码:
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;
void FathEXIT(int sig); // 父进程的信号处理函数。
void ChldEXIT(int sig); // 子进程的信号处理函数。
int main()
{
// 忽略全部的信号,不希望被打扰。
for (int ii=1;ii<=64;ii++) signal(ii,SIG_IGN);
// 设置信号,在shell状态下可用 "kill 进程号" 或 "Ctrl+c" 正常终止些进程
// 但请不要用 "kill -9 +进程号" 强行终止
signal(SIGTERM,FathEXIT); signal(SIGINT,FathEXIT); // SIGTERM 15 SIGINT 2
while (true)
{
if (fork()>0) // 父进程的流程。
{
sleep(5); continue;
}
else // 子进程的流程。
{
// 子进程需要重新设置信号。
signal(SIGTERM,ChldEXIT); // 子进程的退出函数与父进程不一样。
signal(SIGINT ,SIG_IGN); // 子进程不需要捕获SIGINT信号。
while (true)
{
cout << "子进程" << getpid() << "正在运行中。\n"; sleep(3); continue;
}
}
}
}
// 父进程的信号处理函数。
void FathEXIT(int sig)
{
// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断。
signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
cout << "父进程退出,sig=" << sig << endl;
kill(0,SIGTERM); // 向全部的子进程发送15的信号,通知它们退出。
// 在这里增加释放资源的代码(全局的资源)。
exit(0);
}
// 子进程的信号处理函数。
void ChldEXIT(int sig)
{
// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断。
signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
cout << "子进程" << getpid() << "退出,sig=" << sig << endl;
// 在这里增加释放资源的代码(只释放子进程的资源)。
exit(0);
}