守护进程「建议收藏」

守护进程「建议收藏」[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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 谷歌的营销策略分析_反谷歌法

    谷歌的营销策略分析_反谷歌法谷歌YSlow准则YSlow可以对网站的页面进行分析,并告诉你为了提高网站性能,如何基于某些规则而进行优化。测试个人站点通过测试个人站点可以获得下面的数据23条准则MakefewerHT…

    2025年7月7日
    4
  • SpringMVC框架工作流程图及工作原理

    SpringMVC框架工作流程图及工作原理SpringMVC框架的工作原理图:SpringMVC的具体工作原理1、客户端用户发送请求至前端控制器DispatcherServlet。2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。3、HandlerMapping处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给Dispatc…

    2022年6月7日
    31
  • JVM调优工具总结「建议收藏」

    JVM调优工具总结「建议收藏」一、jps:虚拟机进程状况工具它可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(MainClass,main()函数所在的类)名称以及正在运行的本地虚拟机唯一ID(LVMID);它是使用率最高的一个JDK命令行工具,因为其他的命令行工具都需要输入查询到的ID来确定要监控的是哪一个虚拟机进程。命令格式:jps[options][hostid]选项作用-q只…

    2022年5月6日
    28
  • 圣诞节来了,怎能还没有圣诞树呢 快来为心爱的她送上专属的圣诞礼物叭~

    圣诞节来了,怎能还没有圣诞树呢 快来为心爱的她送上专属的圣诞礼物叭~圣诞节来了,怎能没有圣诞树!作为我的粉丝朋友们,我不允许大家还没有专属于自己的圣诞树!我要让大家收到最特别最美丽的圣诞树!

    2022年7月25日
    7
  • java 4种 布局方法_JAVA布局模式:GridBagConstraints终极技巧

    java 4种 布局方法_JAVA布局模式:GridBagConstraints终极技巧JAVA布局模式:GridBagConstraints终极技巧(2006-11-1421:07:33)最近正在修改《公交线路查询系统》,做系统的时候都是用NULL布局,由于NULL布局调用windows系统的API,所以生成的程序无法在其他平台上应用,而且如果控件的数量很多,管理起来也比较麻烦,最近我发现一个非常强大的布局模式:GridBagConstraints布局,先发一个实例:gridx…

    2025年10月13日
    3
  • 表结法和账结法_我国采用表结法还是账结法

    表结法和账结法_我国采用表结法还是账结法为了计算出当期利润,通常采用两种方法:表结法和账结法。国内一些财务系统多采用账结法,这似乎比较符合中国式习惯。即:期末,损益类的虚账户的余额结转汇总到本年利润科目,最后账户无余额;资产负债类实账户结

    2022年8月4日
    6

发表回复

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

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