守护进程「建议收藏」

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


相关推荐

  • html静态网页设计代码_静态网页设计心得

    html静态网页设计代码_静态网页设计心得第一周:HTML写静态网页1.HTML理论介绍及常用格式(1).HTML主体格式&lt;!DOCTYPEhtml&gt;&lt;html&gt; &lt;head&gt;  &lt;metacharset="utf-8"/&gt;  &lt;title&gt;&lt;/title&gt; &lt;/head&gt; &lt;body&gt;   &

    2022年9月21日
    0
  • 云生态:云计算棋局中的“胜负手”

    云生态:云计算棋局中的“胜负手”

    2022年3月6日
    32
  • 检测计算机命令,磁盘检测命令chkdsk的使用方法

    检测计算机命令,磁盘检测命令chkdsk的使用方法经常看到有很多朋友在系统开机的时候因系统自检程序时间过长而往往直接跳过磁盘检测 而直接跳过这种心急的想法有时候会引来大祸患 今天我们介绍的 chkdsk 命令是系统自带的系统修复命令 可以帮助我们解决很多常见问题 它可以帮助恢复系统文件 有时可以延缓硬盘的寿命 对此想要有了解的朋友跟随小编一起来看看吧 磁盘检测的原因多是由于系统检测到磁盘数据调用错误或者数据文件丢失而自发引起的一种自我保护措施 使用磁

    2025年6月19日
    0
  • #利用DialogResult属性实现主程序的打开当前窗口的关闭

    #利用DialogResult属性实现主程序的打开当前窗口的关闭利用DialogResult属性实现主程序的打开当前窗口的关闭首先介绍一下非模式化窗体show()和模式化窗体showdialog()的概念:两种方法都能打开显示窗体,1.非模式化窗体show()建立新窗口后仍能对原窗口进行操作,比如点击frm窗口上的“登录”按钮,会弹出FrmMain窗口,弹出后仍能对原窗口frm进行操作(移动,点击登录按钮等操作)2.模式化窗体showdialog()建…

    2022年6月22日
    23
  • 通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!

    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!Unity,游戏。本文设计了一个第一人称射击游戏的Demo开发过程,文中对射击游戏的基础功能做了一个详细介绍,适用于一个基础框架,可以用于一个射击游戏的完整开发,请品尝。

    2022年4月27日
    31
  • python 0xff_正在解压缩“0xff”与“\xff”[通俗易懂]

    python 0xff_正在解压缩“0xff”与“\xff”[通俗易懂]我试图用wave库从wave文件中读取二进制数据。数据以’\x0f\x06\x0a…’的形式读取和报告,我想把十六进制数转换成整数(你知道,以10为基数)。我把这些字符当作十六进制字符来处理,但我并没有把这些字符当作十六进制字符来处理。在importwaveimportstructpath=”C:\\directory\\file.wav”file=wave.open(path,’r’)dat…

    2022年6月19日
    28

发表回复

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

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