06进程间关系-学习笔记

Orphan Process孤儿进程

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

06进程间关系-学习笔记_第1张图片

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

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

getpid();
getppid();
ps aux #查看进程详细信息
ps ajx #查看进程关系
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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;
}

孤儿进程检查

06进程间关系-学习笔记_第2张图片

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

process.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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子进程组是,要使用函数而不是重载命令

进程间三种关系

亲缘关系

06进程间关系-学习笔记_第3张图片

process Group进程组关系

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

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

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

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

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

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

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

06进程间关系-学习笔记_第4张图片

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

    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接收,杀死应用进程

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

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

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

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

孤儿进程,多进程模型中父进程异常关闭,导致子进程失去托管,这类进程为孤儿进程。
开发者关闭其父进程,让子进程脱离终端控制,此进程工作于后台,周期执行,这类进程称为守护进程(也是孤儿)

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

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

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

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

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

守护进程执行的三种模式

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

shell脚本实现开机启动

守护进程的开发流程

  1. 重定向STD_FILENO06进程间关系-学习笔记_第5张图片

  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中写入系统时间

你可能感兴趣的:(linux,笔记,linux)