前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内核编程--进程组和守护进程

Linux内核编程--进程组和守护进程

作者头像
Coder-ZZ
发布2022-05-09 21:41:38
2.9K0
发布2022-05-09 21:41:38
举报
文章被收录于专栏:C/C++进阶专栏

会话和进程组

进程组:进程组是多个进程的集合, 接收同一个终端的各类信号信息。进程调用setpgid(pid, pgid)可以加入一个现有的进程组或者创建一个新的进程组。

进程组长(头部进程):每个进程组都有一个进程组ID, 每个进程组都有一个组长(头部进程), 在大部分系统中, 进程组ID一般就是头部进程ID。获得一个进程所在的进程组ID用getpgid(pid)。

作业:是一个进程组,作业分为前台作业(前台进程组),后台作业(后台进程组)

会话:会话是若干进程组的集合。会话有一个前台进程组和多个后台进程组。对于一个打开的控制终端,会话 开始于用户登录,终止于用户退出。

会话的创建:

进程调用setsid函数创建一个新的Session,并成为Session Leader

a.调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。

*要保证当前进程不是进程组的Leader也很容易,只要先fork再调用setsid就行了。fork创建的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid就不会有问题了。

b.进程调用完此函数,会成为新会话的会话首进程(session leader),该进程会成为一个新进程组的头部进程,新进程组的组ID就是这个进程的ID。

c.该进程没有控制终端。如果在调用setsid之前该进程有一个控制终端,那么这种联系会被切断。

创建会话代码样例:

代码语言:javascript
复制
#define _POSIX_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

void main() {
  pid_t pid;
  int p[2];
  char c='?';

  if (pipe(p) != 0)
    perror("pipe() error");
  else
    if ((pid = fork()) == 0) {
      printf("child's process group id is %d\n", (int) getpgrp());
      write(p[1], &c, 1);
      setsid();
      printf("child's process group id is now %d\n", (int) getpgrp());
      exit(0);
    }
    else {
      printf("parent's process group id is %d\n", (int) getpgrp());
      read(p[0], &c, 1);
      sleep(5);
    }
}

运行结果:

会话与控制终端

一个会话可以有一个控制终端,控制终端通常是终端设备,比如登录后的xshell界面。

与控制终端建立连接的会话首进程被称为控制进程。

对于有控制终端的会话,同一时刻只能有一个进程组能够称为前台进程组,会话中的其他进程组都是后台进程组。

控制终端与会话的信号传递

在终端输入中断键(Delete或Ctrl+C), 会将中断信号( SIGINT)发送给前台进程组的所有进程。

在终端输入退出键(Ctrl+\), 会将退出信号( SIGQUIT)发送给前台进程组的所有进程。

如果终端已经断开连接,会将挂断信号( SIGHUP)发送给控制进程。

示意图

守护进程

守护进程的概念

守护进程是运行在操作系统后台的特殊进程,且守护进程和控制终端(比如xshell界面)是隔离的。

守护进程没有控制终端通常源于它们由系统初始化脚本启动,然而守护进程也可能从某个终端由用户在shell提示符下键入命令行启动,这样的守护进程必须亲自脱离于控制终端的关联,从而避免与作业的控制、终端会话管理、终端产生信号等发生任何不期望的交互,也可以避免在后台运行的守护进程非预期地输出到终端。

守护进程的特点:

无需控制终端

运行在后台

一般随操作系统启动和关闭

守护进程运行在后台,但是和后台进程有一定区别:

(1)守护进程和终端不挂钩,不向终端输出内容(信息),但后台进程是能向终端输出内容的(如使用printf语句,后台进程可以将内容输出到屏幕上)。

(2)守护进程在关闭终端(比如Xshe11)的时候不会受到影响,而后台进程将在终端关闭后自动退出。

守护进程的启动方式:

1.在系统启动阶段,由系统初始化脚本启动。

这些脚本通常位于/etc目录或以/etc/rc开头的某个目录中。由这些脚本启动的守护进程一开始时拥有超级用户权限。

比如inetd, sendmail, syslogd这些服务的进程都是用系统初始化脚本启动

2.当有网络请求(Telnet, FTP)到达时,由inetd超级服务器启动

3.cron命令启动

4.at命名启动

5.从用户终端(或在前台/后台)执行测试/重启服务的脚本启动

*由于守护进程没有控制终端,因此如果守护进程需要输出日志或消息,需要借助syslog/openlog 函数。

操作系统中常见的守护进程分类:

系统守护进程:syslogd、login、crond、at等。

网络相关的守护进程:sendmail、httpd、xinetd等。

独立启动的守护进程:httpd、named、xinetd等。

守护进程的创建方式:

让一个普通进程变成守护进程的操作步骤:

Step.1 调用fork( )。

Step.2 在父进程中,调用exit( )来终止父进程,留下子进程继续运行。

子进程继承了父进程的进程组ID,不过它有自己的进程ID,这就保证了子进程不是一个进程组的头部进程

Step.3 调用setsid( ),给守护进程创建一个新的进程组和会话,使当前进程成为新进程组的头部进程。

Step.4 忽略SIGHUP信号,并再次调用fork。该函数返回时,父进程实际上是上一次调用fork产生的子进程。然后这个父进程被终止,留下新的子进程继续运行。

忽略SIGHUP信号的原因:当头部进程(第一次fork产生的子进程)终止时,其会话中的所有进程(再次fork产生的子进程)都会收到SIGHUP信号。

Step.5 把工作目录改到根目录chdir( )。这是因为守护进程的工作目录可以位于文件系统的任何位置。守护进程在正常运行时,会保持某个随机目录处于打开状态,从而阻止管理员卸载包含了该目录的文件系统。这么做是为了统一路径,让文件系统可拆卸。

Step.6 关闭所有文件描述符。

Step.7 打开文件描述符stdin,stdout和stderr并将它们重定向到/dev/null。

让一个普通进程变成守护进程的常用函数: daemon_init()

代码样例:

代码语言:javascript
复制
int daemon_init(const char *pname, int facility)
{
  int i;
  pid_t pid;

  if ((pid = Fork()) < 0)
    return (-1);
  else if (pid)
    _exit(0);     /* parent terminates */

  /* child 1 continues... */
  if (setsid() < 0)     /* become session leader */
    return (-1);

  Signal(SIGHUP, SIG_IGN);
  if ((pid = Fork()) < 0)
    return (-1);
  else if (pid)
    _exit(0);     /* child 1 terminates */

  /* child 2 continues... */
  daemon_proc = 1;      /* for err_XXX() functions */

  chdir("/");       /* change working directory */
  /* close off file descriptors */
  for (i = 0; i < MAXFD; i++)
    close(i);

  /* redirect stdin, stdout, and stderr to /dev/null */
  open("/dev/null", O_RDONLY);
  open("/dev/null", O_RDWR);
  open("/dev/null", O_RDWR);

  openlog(pname, LOG_PID, facility); //使用sylogd处理错误

  return (0);       /* success */
}

简洁版:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

int main(int argc, char* argv[])
{
    pid_t pid = 0;
    pid_t sid = 0;
    FILE *fp= NULL;
    int i = 0;
    pid = fork();// fork a new child process

    if (pid < 0)
    {
        printf("fork failed!\n");
        exit(1);
    }

    if (pid > 0)// its the parent process
    {
       printf("pid of child process %d \n", pid);
        exit(0); //terminate the parent process succesfully
    }

    umask(0);//unmasking the file mode

    sid = setsid();//set new session
    if(sid < 0)
    {
        exit(1);
    }

    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    fp = fopen("mydaemonfile.txt", "w+");
    while (i < 10)
    {
        sleep(1);
        fprintf(fp, "%d", i);
        i++;
    }
    fclose(fp);
  
    return (0);
}

运行结果:

常用的Linux进程分析命令:

代码语言:javascript
复制
查看正在运行的进程:ps -ef

查看当前的所有进程:ps axj

查看占用端口的进程:lsof -i:8086

查看用户username的进程所打开的文件:lsof -u username

查询被进程ID对应的进程打开的文件:lsof -p 1000

查看进程内存情况:pmap PID

样例:

参考阅读:

《UNIX环境高级编程第3版》

《Linux C++ 通信架构实战》

《UNIX网络编程 卷1:套接字联网API 第3版》

https://www.ibm.com/docs/en/zos/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员与背包客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档