06进程间关系-学习笔记

Orphan Process孤儿进程

父进程先于子进程退出,子进程失去托管,这种子进程统称为孤儿进程

  • 失效进程(孤儿进程):导致内存泄漏,影响新进程的创建
  • 孤儿进程的危害不可预测,如果一个孤儿进程持续的申请系统资源,这导致影响系统稳定性

编写一个孤儿进程检测处理模型

c 复制代码
getpid();
getppid();
bash 复制代码
ps aux #查看进程详细信息
ps ajx #查看进程关系
c 复制代码
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    pid_t pid;
    pid = fork();
    if (pid > 0)
    {
        sleep(30);
        exit(0);
    }
    else if (pid == 0)
    {
        printf("child pid = %d ,ppid = %d", getpid(), getppid());
        sleep(32);
        printf("child pid = %d ,ppid = %d", getpid(), getppid());
        while(1)
        {
            sleep(1);
        }
    }
    else
    {
    }
    return 0;
}

孤儿进程检查

创建一个独立的check进程。父进程通过管道将自己的pid传给check进程。

process.c

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <sys/wait.h>

int main()
{
    // 创建五个子进程
    pid_t pid;
    int i = 0;
    for (i; i < 5; i++)
    {
        pid = fork();
        if (pid == 0)
            break;
    }

    // 父进程
    if (pid > 0)
    {
        int wfd;
        char str_pid[10];
        bzero(str_pid, 10);
        if ((wfd = open("fifo", O_RDWR)) == -1)//两端都是RDWR权限,否则会check端会因为process端结束而发现管道关闭,出现异常。
        {
            perror("open failed");
            exit(0);
        }
        sprintf(str_pid, "%d", getpid());
        write(wfd, str_pid, strlen(str_pid));
        sleep(20);
        exit(0);

        wait(NULL);
    }
    else if (pid == 0)
    {
        printf("child pid %d running..\n", getpid());
        while (1)
        {
            sleep(1);
        }
    }
    else
    {
        perror("fork call failed");
        exit(0);
    }
    return 0;
}

check.c

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <signal.h>
#include <errno.h>

int main()
{
    int rfd;
    if ((rfd = open("fifo", O_RDWR)) == -1)//使用RDWR权限,防止写端关闭,管道权限不够而关闭
    {
        perror("打开管道失败");
        exit(0);
    }
    printf("open成功\n");

    char buf[10];
    bzero(buf, sizeof(buf));
    read(rfd, buf, sizeof(buf));
    pid_t pid = atoi(buf);

    printf("check process pid: %d ,get Parent pid: %d..\n", getpid(), pid);

    // 非阻塞读取管道
    // 1、获取文件属性
    int flag;
    fcntl(rfd, F_GETFL, &flag);
    flag |= O_NONBLOCK; // 设置文件属性为非阻塞
    fcntl(rfd, F_SETFL, flag);

    int len;
    int esrch;
    char str_pid[10];
    bzero(str_pid, 10);

    while ((len = read(rfd, buf, sizeof(buf))) == -1)
    {
        // EAGAIN:这意味着"现在没有可用的数据,以后再试一次" 。
        //非阻塞返回
        if (errno == EAGAIN)
        {

            kill(pid, 0); // 尝试杀一下这个进程
            if (errno == ESRCH)
            {
                // 要杀的进程已经不存在了
                printf("check parent %d its dead\n", pid);
                pid -= pid * 2;

                // sprintf(str_pid,"%d",pid);
                printf("group id %d\n", pid);

                // 杀死孤儿进程
                kill(pid, 9);//函数kill比重载kill命令更合理
                exit(0);
            }
            printf("check parent alive\n");
        }
        else
        {
            perror("read call failed");
            exit(0);
        }
        sleep(1);
    }
    if (len > 0)
    {
        printf("parent exit , check_process Done\n");
        exit(0);
    }
    return 0;
}

注意要点

  1. 双端访问权限都要是RDWR
  2. "通过尝试kill父进程,来判断父进程是否存在",不要放在判断语句里。判断errno的值
  3. kill子进程组是,要使用函数而不是重载命令

进程间三种关系

亲缘关系

process Group进程组关系

  • 为了方便管理系统中大量进程,设计了进程组结构,属于一种管理概念

  • 进程组是由一个组长进程和多个组员进程构成、PID(Process ID)、PPID(Parent Process ID)和PGID(Process Group ID)

  • 每个终端进程都是组长进程

  • 组长进程标志,pid == pgid,此进程为组长进程

  • 进程组的生命周期较长,与某个特定的进程无关,直到组中最后一个进程终止或转移,进程组为空时,系统释放进程组

  • 根据就近原则,组长进程创建的子进程,这些子进程会归纳到父进程同组变为组员进程

  • 进程组关系与亲缘关系没有必然联系,因为组成员可以转移的

  • 创建进程组只有组员进程可以完成,组长无法成功

    c 复制代码
    getpgrp(); //返回当前进程的pgid(组id)
    
    setpgid(pid_tpid,pid t gpid); //此函数可以创建组或转移组中进程
    setpgid(getpid(),getpid()); //申请组id,创建组,只有组员进程可以成功
    setpgid(getpid(0,1000); //转移进程到目标组中去
  • 转移进程要保证对目标组有足够的访问权限 ,其次目标组要存在

process Session会话关系

  • 会话关系便于终端或系统管理终端进程和终端子进程

  • 会话由一个会话发起者和多个会话参与者构成

  • 会话发起者退出,以进程组为单位杀死参与者

  • 只杀死终端进程为首的一组进程

  • 会话发起者的标志 pid == pgid == sid(会话id)

  • 后续开发app时,要进行脱离控制终端,避免会话发起者bash接收,杀死应用进程

    • 脱离终端:让组员进程创建新组,避免杀死

    • 创建新会话脱离原有会话

      c 复制代码
      getsid(getpid());//获取当前进程的会话id
      setsid();//当前进程创建新会话,创建新会话只有组员进程能完成,因为此函数中会进行进程组创建,如果是组长此步骤不会成功
    • 终端进程无法脱离终端,必然受终端控制,因为它无法创建组也无法成立新会话

另一种孤儿进程------守护进程Daemon Process

孤儿进程,多进程模型中父进程异常关闭,导致子进程失去托管,这类进程为孤儿进程。

开发者关闭其父进程,让子进程脱离终端控制,此进程工作于后台,周期执行,这类进程称为守护进程(也是孤儿)

守护进程(也叫精灵进程)和普通进程的差异性

  1. 守护进程的生命周期比普通进程长,守护进程的生命周期随操作系统持续,开机自动启动,关机则关闭

  2. 守护进程为主程序提供服务和支持,保证主程序的稳定性

  3. 守护进程不允许参与前台任务,也不允许将数据打印到前台

  4. 低效模式运行,不允许占用大量系统资源

守护进程执行的三种模式

  1. 间隔执行
  2. 定时执行
  3. 条件触发

shell脚本实现开机启动

守护进程的开发流程

  1. 重定向STD_FILENO

  2. 造孤儿------父进程创建子进程、父进程退出

  3. 子进程脱离控制终端,创建新会话

  4. 关闭前台描述符STDIN_FILENO,STDOUT_FILENO,

    1. STD_FILENO默认是perror("xxx call faild") 标准错误是占用标准输出的,会将异常信息显示在终端上(前台)
    2. 为了避免错误信息抛出到前台,可以采用重定向的方式,将错误信息抛出到文件中
  5. 修改进程的工作目录为根目录

    默认情况下进程的工作目录,是程序所在位置,进程访问文件,访问磁盘信息都是以工作目录为基准

  6. 修改进程的Umask掩码,为0002

  7. 执行守护进程任务

  8. 守护进程的退出处理

demo,编写一个守护进程,后台执行,开机启动,每间隔3s向time.log中写入系统时间

相关推荐
LuDvei1 分钟前
LINUX错误提示函数
linux·运维·服务器
未来可期LJ7 分钟前
【Linux 系统】进程间的通信方式
linux·服务器
Abona9 分钟前
C语言嵌入式全栈Demo
linux·c语言·面试
Lenyiin22 分钟前
Linux 基础IO
java·linux·服务器
The Chosen One98527 分钟前
【Linux】深入理解Linux进程(一):PCB结构、Fork创建与状态切换详解
linux·运维·服务器
Kira Skyler1 小时前
eBPF debugfs中的追踪点format实现原理
linux
2501_927773072 小时前
uboot挂载
linux·运维·服务器
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [drivers][dma]dmapool
linux·笔记·学习
goxingman3 小时前
在 Linux 中查看磁盘运行占用(I/O 使用率)
linux·运维·chrome
STCNXPARM3 小时前
Linux camera之Media子系统
linux·camera·v4l2·media子系统