赞
踩
进程组和会话在 UNIX 系统中是非常重要的概念,特别是在进行作业控制和终端会话管理时。下面是关于进程组和会话的详细解释:
定义与作用:
cmd1 | cmd2
)中的所有进程可以属于同一个进程组。进程组首进程:
setpgid()
)修改自己或其他进程的进程组归属。生命周期:
会话(session)管理是 UNIX 系统中处理进程组和终端交互的重要机制。这里提供对您提到的各个点的更深入的解释:
setsid()
系统调用来显式创建一个新会话。通过这些机制,UNIX 系统提供了强大的工具来管理和控制多个进程和它们的交互,这对于构建稳定和可管理的多用户环境是必要的。
在 UNIX 系统中,进程组、会话和控制终端之间的关系是协调进程之间通信和控制的关键机制。这些概念相互作用,支持复杂的作业控制和会话管理,特别是在多用户环境中。下面是这些概念之间关系的详细解释:
进程组是一种逻辑分组,用于将一系列相关的进程组织在一起,以便于统一管理。通常情况下,这些进程是从同一个命令行启动的,比如一个 shell 脚本中的多个命令。进程组内的每个进程都共享同一个进程组标识符(PGID),使得操作系统可以方便地对整个组发送信号。
会话是一或多个进程组的更高级别的集合。每个会话可以包含多个进程组,包括前台和后台进程组。会话通常在用户登录时开始,用户的登录 shell 成为会话首进程,并且会创建一个新的会话。这个会话首进程有权申请或分配一个控制终端。
控制终端是会话首进程与用户交互的界面。它通常是一个物理或虚拟的终端设备(如终端窗口)。会话首进程在首次打开一个终端设备时,该设备成为该会话的控制终端。控制终端可以被用来向会话中的进程发送信号(例如,当用户按下 Ctrl+C 时发送 SIGINT)。
这种层次和关系的设置使得 UNIX 系统能够有效地管理多任务操作,特别是在多用户和网络环境中,提供了高度的灵活性和控制能力。
在 UNIX 和类 UNIX 系统中,操作进程组和会话的一些核心函数允许程序员在运行时管理进程的组和会话属性。这些函数非常重要,因为它们提供了控制进程如何与操作系统和其他进程交互的能力。
pid_t getpgrp(void);
pid_t getpgid(pid_t pid);
pid
为 0,则返回调用进程的进程组 ID。pid
:进程标识符。int setpgid(pid_t pid, pid_t pgid);
pid
加入到进程组 pgid
,或者创建一个新的进程组(如果 pid
等于 pgid
)。pid
:要更改进程组的进程的 ID,如果 pid
是 0,则表示调用进程。pgid
:新的进程组 ID,如果 pgid
是 0,则 pid
指定的进程将成为新进程组的首进程。pid_t getsid(pid_t pid);
pid
为 0,则返回调用进程的会话 ID。pid
:进程标识符。pid_t setsid(void);
setpgid()
或 setsid()
时,要注意进程间的权限和状态,因为这些调用可能受到限制,特别是在已经拥有子进程或已属于某个进程组的情况下。setsid()
时,如果调用者已是某个进程组的领头进程,则该调用会失败。这是为了防止一个已有控制终端的进程逃避终端产生的信号。这些函数提供了在 UNIX 类操作系统中管理和控制进程组和会话的基本机制,对于实现作业控制、守护进程以及终端会话管理等功能至关重要。
守护进程(Daemon process)是 Linux 系统中一个非常核心的概念,对于系统的稳定运行和服务的持续提供至关重要。这些进程通常是在系统启动时启动,并在整个系统运行期间持续运行,直到系统关闭。下面详细解释守护进程的特征和其实现方法:
长生命周期:
systemd
、init
)自动启动。在后台运行:
服务性质:
httpd
)、邮件服务器(如 postfix
)和文件服务器(如 smbd
)都是典型的守护进程。这个过程是在 UNIX 和 Linux 系统中常用的模式,用于创建独立于控制终端的后台服务。以下是如何执行这个转换的详细步骤,包括其中涉及的各个系统调用和目的:
这个步骤确保了新的守护进程不是一个会话领导者,从而可以调用 setsid()
创建新会话。
pid_t pid = fork();
if (pid < 0) {
// 处理错误
exit(EXIT_FAILURE);
}
if (pid > 0) {
// 父进程直接退出
exit(EXIT_SUCCESS);
}
子进程调用 setsid()
创建一个新的会话,成为会话的领导者,脱离任何控制终端。
if (setsid() < 0) {
// 处理错误
exit(EXIT_FAILURE);
}
设置 umask 为 0 确保守护进程创建的任何文件和目录都有适当的访问权限。
umask(0);
将当前工作目录改为根目录 /
,这可以防止守护进程阻止卸载文件系统。
if (chdir("/") < 0) {
// 处理错误
exit(EXIT_FAILURE);
}
这可以防止守护进程无意中保持打开的文件描述符,可能导致资源泄露或不必要的行为。
int x;
for (x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
close(x);
}
/dev/null
关闭了标准输入、输出和错误(文件描述符 0, 1, 2)后,常见做法是打开 /dev/null
并用 dup2()
使得这些描述符指向 /dev/null
。这样,任何尝试读写这些文件描述符的操作都不会有任何效果。
int fd = open("/dev/null", O_RDWR);
dup2(fd, 0); // STDIN
dup2(fd, 1); // STDOUT
dup2(fd, 2); // STDERR
在完成所有的设置后,守护进程可以开始执行其核心功能,如周期性检查、处理请求等。
while (1) {
// 执行任务
sleep(1); // 休眠是为了模拟周期性任务
}
以上步骤创建了一个完全独立的守护进程,它在系统后台安静地运行,几乎不受用户会话影响,适用于需要长时间运行并且不需要用户交互的服务。这种类型的进程是服务器和服务架构的基础。
/* 写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中。 */ #include <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <sys/time.h> #include <signal.h> #include <time.h> #include <stdlib.h> #include <string.h> void work(int num) { // 捕捉到信号之后,获取系统时间,写入磁盘文件 time_t tm = time(NULL); struct tm * loc = localtime(&tm); // char buf[1024]; // sprintf(buf, "%d-%d-%d %d:%d:%d\n",loc->tm_year,loc->tm_mon // ,loc->tm_mday, loc->tm_hour, loc->tm_min, loc->tm_sec); // printf("%s\n", buf); char * str = asctime(loc); int fd = open("time.txt", O_RDWR | O_CREAT | O_APPEND, 0664); write(fd ,str, strlen(str)); close(fd); } int main() { // 1.创建子进程,退出父进程 pid_t pid = fork(); if(pid > 0) { exit(0); } // 2.将子进程重新创建一个会话,新的会话会脱离原来的控制终端 setsid(); // 3.设置掩码 umask(022); // 4.更改工作目录 chdir("/home/nowcoder/"); // 5. 关闭、重定向文件描述符 int fd = open("/dev/null", O_RDWR); dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); // 6.业务逻辑 // 捕捉定时信号 struct sigaction act; act.sa_flags = 0; act.sa_handler = work; 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 = 2; val.it_interval.tv_usec = 0; // 创建定时器 setitimer(ITIMER_REAL, &val, NULL); // 不让进程结束 while(1) { sleep(10); } return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。