守护进程「建议收藏」

守护进程「建议收藏」[toc]终端在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(ControllingTerminal),进程中,控制终端是保存在PC

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

终端

在UNIX系统中, 用户通过终端登录系统后得到一个Shell进程, 这个终端成为Shell进程的控制终端(Controlling Terminal), 进程中, 控制终端是保存在PCB中的信息, 而fork会复制PCB中的信息, 因此由Shell进程启动的其它进程的控制终端也是这个终端. 默认情况下(没有重定向), 每个进程的标准输入, 标准输出和标准错误输出都指向控制终端, 进程从标准输入读也就是读用户的键盘输入, 进程往标准输出或标准错误输出写也就是输出到显示器上. 信号中还讲过, 在控制终端输入一些特殊的控制键可以给前台进程发信号, 例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。

Alt + Ctrl + F1、F2、F3、F4、F5、F6–>字符终端
Alt + F7–>图形终端
SSH、Telnet–>网络终端, pts (pseudo terminal slave)指伪终端

终端启动流程

每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端. 事实上每个终端设备都对应一个不同的设备文件, /dev/tty提供了一个通用的接口, 一个进程要访问它的控制终端既可以通过/dev/tty也可以通过该终端设备所对应的设备文件来访问. ttyname函数可以由文件描述符查出对应的文件名, 该文件描述符必须指向一个终端设备而不能是任意文件

简单来说,一个Linux系统启动,大致经历如下的步骤: init --> fork --> exec --> getty -->用户输入帐号 --> login -->输入密码 --> exec --> bash

硬件驱动程序负责读写实际的硬件设备, 比如从键盘读入字符和把字符输出到显示器, 线路规程像一个过滤器, 对于某些特殊字符并不是让它直接通过, 而是做特殊处理, 比如在键盘上按下Ctrl-z, 对应的字符并不会被用户程序的read读到, 而是被线路规程截获, 解释成SIGTSTP信号发给前台进程, 通常会使该进程停止. 线路规程应该过滤哪些字符和做哪些特殊处理是可以配置的

ttyname与ttyname_r

char *ttyname(int fd);
由文件描述符查出对应的文件名

int ttyname_r(int fd, char *buf, size_t buflen);
与上述相同

#include <unistd.h>
#include <stdio.h>
int main(void)
{
    char p[64];
    ttyname_r(0, p, sizeof(p));
    printf("%s\n", p);
    printf("fd 0: %s\n", ttyname(0));
    printf("fd 1: %s\n", ttyname(1));
    printf("fd 2: %s\n", ttyname(2));
    return 0;
}	

/*
/dev/pts/1
fd 0: /dev/pts/1
fd 1: /dev/pts/1
fd 2: /dev/pts/1
*/

进程组

进程组, 也称之为作业. BSD于1980年前后向Unix中增加的一个新特性. 代表一个或多个进程的集合. 每个进程都属于一个进程组. 在waitpid函数和kill函数的参数中都曾使用到. 操作系统设计的进程组的概念, 是为了简化对多个进程的管理

当父进程, 创建子进程的时候, 默认子进程与父进程属于同一进程组. 进程组ID第一个进程ID(组长进程). 所以, 组长进程标识: 其进程组ID其进程ID

可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死

组长进程可以创建一个进程组, 创建该进程组中的进程, 然后终止. 只要进程组中有一个进程存在, 进程组就存在, 与组长进程是否终止无关

进程组生存期: 进程组创建到最后一个进程离开(终止或转移到另一个进程组)

一个进程可以为自己或子进程设置进程组ID

总结

进程组
  进程的组长
    组里边的第一进程
    进程组的ID–> 进程组的组长的ID
  进程组组长的选择
    进程中的第一个进程
  进程组ID的设定
    进程组的id就是组长的进程ID

基础API

getpgrp

pid_t getpgrp(void);
获取当前进程的进程组ID, 总是返回调用者的进程组ID

getpgid

pid_t getpgid(pid_t pid);
如果pid = 0, 那么该函数作用和getpgrp一样。
获取指定进程的进程组ID

setpgid

int setpgid(pid_t pid, pid_t pgid);
改变进程默认所属的进程组. 通常可用来加入一个现有的进程组或创建一个新进程组
注意:
(1)如改变子进程为新的组, 应fork后, exec前。
(2)权级问题. 非root进程只能改变自己创建的子进程, 或有权限操作的进程

会话

会话: 多个进程组

创建一个会话需要注意以下6点注意事项:

  1. 调用进程不能是进程组组长, 该进程变成新会话首进程(session header)
  2. 该进程成为一个新进程组的组长进程
  3. 需有root权限(ubuntu不需要)
  4. 新会话丢弃原有的控制终端, 该会话没有控制终端
  5. 该调用进程是组长进程, 则出错返回
  6. 建立新会话时, 先调用fork, 父进程终止, 子进程调用setsid

基础API

getsid

pid_t getsid(pid_t pid);
获取进程所属的会话ID

成功:返回调用进程的会话ID;失败:-1,设置errno

pid为0表示察看当前进程session ID

ps ajx命令查看系统中的进程。参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。

组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。

setsid

pid_t setsid(void);
创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。成功:返回调用进程的会话ID;失败:-1,设置errno
调用了setsid函数的进程,既是新的会长,也是新的组长

守护进程

Daemon(精灵)进程, 是Linux中的后台服务进程, 通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件. 一般采用以d结尾的名字

Linux后台的一些系统服务进程, 没有控制终端, 不能直接和用户交互. 不受用户登录和注销的影响, 一直在运行着, 他们都是守护进程. 如: 预读入缓输出机制的实现; ftp服务器; nfs服务器等

创建守护进程,最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。

守护进程的特点
  后台服务程序
  独立于终端控制
  周期性执行某任务
  不受用户登陆注销影响
  一般采用以d结尾的名字(服务)

创建守护进程模型

  1. fork子进程, 父进程退出, 所有工作在子进程中进行形式上脱离了控制终端; 必须
  2. 子进程创建新会话, setsid函数, 使子进程完全独立出来, 脱离控制; 必须
  3. 改变当前目录为根目录, chdir()函数, 防止占用可卸载的文件系统, 也可以换成其它路径, 为了增强程序的健壮性; 非必须
  4. 重设文件权限掩码, umask()函数, 防止继承的文件创建屏蔽字拒绝某些权限, 增加守护进程灵活性; 非必须
  5. 关闭文件描述符, 继承的打开文件不会用到, 浪费系统资源, 无法卸载, close(0), close(1), close(2); 非必须
  6. 执行核心工作
  7. 守护进程退出处理程序模型;

示例程序

函数使用

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>

int main(int argc, const char * argv[]) {
    // 创建一个会话
    // 将子进程变为会长
    pid_t pid = fork();
    if (pid > 0) {
        exit(1);
        kill(getpid(), SIGKILL);
        raise(SIGKILL);
        abort();
    }
    else if (pid == 0) {
        // 变为会长
        // 会长变为守护进程
        setsid();
        while (1);
    }
    return 0;
}

练习

写一个守护进程, 每隔2s获取一次系统时间, 将这个时间写入到磁盘文件
思路:
  创建守护进程
  需要一个定时器, 2s触发一次, setitimer
  信号捕捉

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
#include <time.h>
#include <fcntl.h>  

void dowork(int nu) {
    // 等到当前时间
    time_t curtime;
    time(&curtime);
    // 格式化时间
    char *p = ctime(&curtime);
    // 将时间写入文件
    int fd = open("/home/zyb/time.txt", O_CREAT | O_WRONLY | O_APPEND, 0664);
    write(fd, p, strlen(p) + 1);  // 加入\0
    close(fd);
}

int main(int argc, const char * argv[]) {
    pid_t pid = fork();
    if (pid > 0) {
        exit(1);
        kill(getpid(), SIGKILL);
        raise(SIGKILL);
        abort();
    }
    else if (pid == 0) {
        // 变为会长, 脱离控制终端, 变为守护进程
        setsid();
        // 改变工作目录
        chdir("/home/zyb");
        // 重设文件掩码
        umask(0);
        // 关闭文件描述符
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
        // 执行核心操作
        // 注册信号捕捉
        struct sigaction act;
        act.sa_flags = 0;
        act.sa_handler = dowork;
        sigemptyset(&act.sa_mask);
        sigaction(SIGALRM, &act, NULL);
        // 创建定时器 
        struct itimerval val;
        // 第一次触发事件
        val.it_value.tv_sec = 2;
        val.it_value.tv_usec = 0;
        // 定时间隔
        val.it_interval.tv_sec = 1;
        val.it_interval.tv_usec = 0;
        setitimer(ITIMER_REAL, &val, NULL);
        // 保证子进程处于运行状态
        while (1);
    }
    return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/166839.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 显示出圆周率后一百位_搜索圆周率

    显示出圆周率后一百位_搜索圆周率这里不给发那么多字数,具体查看:https://tys.ink/?p=5221

    2025年11月4日
    4
  • 基于Python的频谱分析(一)

    基于Python的频谱分析(一)

    2021年11月21日
    44
  • android 参数 attrs.xml,android – 定义自定义attrs

    android 参数 attrs.xml,android – 定义自定义attrs传统的方法充满了样板代码和笨拙的资源处理。这就是我制作Spyglass框架的原因。为了演示它是如何工作的,这里有一个示例,展示如何创建一个显示字符串标题的自定义视图。第1步:创建自定义视图类。publicclassCustomViewextendsFrameLayout{privateTextViewtitleView;publicCustomView(Contextcont…

    2022年8月31日
    3
  • 如何画UML类图

    如何画UML类图简单介绍统一建模语言 UnifiedModel UML 是用来设计软件蓝图的可视化建模语言 面向对象的建模语言的国际标准 它的特点是简单 统一 图形化 能表达软件设计中的动态与静态信息 类 接口 类图类类 Class 是指具有相同属性 方法和关系的对象的抽象 它封装了数据和行为 是面向对象程序设计 OOP 的基础 具有封装性 继承性和多态性等三大特性 在 UML 中 类使用包含类名 属性和操作且带有分隔线的矩形来表示 类名类名 Name 是一个字符串 例如

    2025年12月12日
    5
  • goland2021.11.4 激活【中文破解版】「建议收藏」

    (goland2021.11.4 激活)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html…

    2022年3月29日
    42
  • 什么是AV接口_A∨插口的作用

    什么是AV接口_A∨插口的作用Audio&Video音频&视频:音频(Audio)这个专业术语,人类能够听到的所有声音都称之为音频,它可能包括噪音、声音被录制下来以后,无论是说话声、歌声、乐器都可以通过数字音乐软件处理。视频(Video)泛指将一系列静态影像以电信号方式加以捕捉,纪录,处理,储存,传送,与重现的各种技术。连续的图像变化每秒超过24帧(frame)画面以上时,根据视觉暂留原理,人眼无法辨别单幅的静态画面;看上去是平滑连续的视觉效果,这样连续的画面叫做视频。由于AV算是出现比较早的一种接口,它由黄.

    2022年9月22日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号