守护进程「建议收藏」

守护进程「建议收藏」[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;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 重绘与回流_html回流重绘

    重绘与回流_html回流重绘文章目录css图层图层创建的条件重绘(Repaint)回流触发重绘的属性触发回流的属性常见的触发回流的操作优化方案requestAnimationFrame—-请求动画帧写在最后css图层浏览器在渲染一个页面时,会将页面分为很多个图层,图层有大有小,每个图层上有一个或多个节点。也就是我们各种各样的Dom标签在渲染DOM的时候,浏览器所做的工作实际上是:1.获取DOM后分割为多个图层2.对每个图层的节点计算样式结果 (Recalculatestyle–样式重计算)3.为每个节点生

    2022年10月24日
  • Git 切换分支命令

    Git 切换分支命令从Github上clone下来的项目都是主分支branch,为了开发的安全性,如何切换到其它分支呢?gitbranch查看本地分支*表示当前所处的分支,如下图所示:gitbranch-a查看项目所有分支:gitcheckout-b切换分支,例如我切换到stardard-base-4.x-dev:gitcheckout-bstandard-base-4.x-devorigin/standard-base-4.x-dev第二次切换直接填入分支名称即可。

  • 屏幕尺寸、分辨率、像素密度及其三者之间的关系[通俗易懂]

    屏幕尺寸、分辨率、像素密度及其三者之间的关系[通俗易懂]屏幕尺寸屏幕对角线的长度单位英寸,1英寸等于2.54厘米分辨率横纵向方向像素的大小纵向像素*横向像素,如1920px*1080px单位像素(px)像素密度指每英寸屏幕所拥有的像素的数量单位dpi三者关系像素密度=Sqrt(横向像素*横向像素+纵向像素*纵向像素)/屏幕尺寸注意:像素大小并不是固定的,不同的设备像素的大小可能不相同

  • C语言学生成绩管理系统设计 《C语言程序设计》实训报告[通俗易懂]

    C语言学生成绩管理系统设计 《C语言程序设计》实训报告[通俗易懂]一、课程设计题目《学生成绩管理系统设计》二、实训目的(1)掌握结构化程序设计的基本方法,基本掌握面向对象程序设计的基本思路和方法。(2)掌握C++的基本概念和基础知识。(3)通过训练能够读懂较为复杂的C++语言源程序并具备基本C++语言程序设计的能力。(4)通过实训,培养自己编写、调试、分析程序的能力。(5)培养自己独立解决问题,查找资料的能力。同学之间相互帮助,相互交流,相互合作的团队精神。(6)培养自己良好的学习兴趣,独立的编程风格。(7)通过实训检查自己学习上的

  • Spring源代码由浅入深系列五 GetBean

    Spring源代码由浅入深系列五 GetBean

  • mybatis log plugin 激活码[最新免费获取][通俗易懂]

    (mybatis log plugin 激活码)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html1M3Q9SD5XW-eyJsa…

发表回复

您的电子邮箱地址不会被公开。

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