目录
3. SIGTERM (Termination signal)
5. SIGCHLD (Child terminated signal)
6. SIGPIPE (Broken pipe signal)
ps ajx |head -1 && ps ajx |grep tcpserver
再打开一个xshell就等于重新启动一个会话,还能看到tcpserver进程在运行
daemon() 与 setsid() 和 fork() 的比较
守护进程
前台进程 后台进程
后台进程不能标准输入
3个后台进程了
把2号任务提到前台进程
ctrl c终止前台进程
把任务提到前台进程,后悔了,再重新放回后台
ctrl z就可以暂停,然后系统自动把bash提到前台
bg 3再把3号进程启动起来
session(进程会话)
在Linux中,session
(会话)通常指的是与用户交互的一个环境,它是系统中与某个用户交互的一系列活动的集合。会话在Linux系统中有多种用途,下面是几种常见的会话类型及其相关概念:
1. 登录会话(Login Session)
当用户通过登录界面(如终端或图形界面)登录到系统时,系统会为该用户创建一个会话。登录会话包括:
-
用户身份验证(通过用户名和密码等方式)。
-
运行用户的默认Shell(例如
bash
)。 -
用户环境变量的设置(例如
$PATH
、$HOME
等)。
这种会话通常由登录管理器(如login
、sshd
或gdm
等)管理。当用户退出登录时,该会话会结束。
2. 进程会话(Process Session)
在Linux中,每个进程都有一个会话(Session),这个会话由session leader
(会话领导进程)控制。进程会话的特征包括:
-
每个进程在启动时都会被分配一个会话ID。
-
会话通常由一个进程创建,称为会话领导进程。
-
会话通常用于进程组管理,特别是在控制终端和后台进程之间的交互。
会话的管理由setsid()
系统调用进行,当进程调用setsid()
时,它会创建一个新的会话,并成为该会话的领导进程
前台任务和后台任务比较好
本质
任务里有多个进程组
每多建一个就多一个
绘画和终端都关掉了,那些任务仍然在
bash也退了,然后就托孤了
受到了用户登录和退出的影响
守护进程化---不想受到任何用户登陆和注销的影响
如何做到(setsid)(创建新会话)
怎么保证自己不是组长
守护进程本质(孤儿进程)
守护进程忽略的几个信号和含义
在 C/C++ 中,守护进程通常会忽略一些信号,确保其在后台继续运行,而不被用户的操作或其他系统事件干扰。除了常见的信号外,SIGPIPE
也是一个重要的信号,守护进程通常会忽略它。以下是几个常见的守护进程忽略的信号及其作用,包括 SIGPIPE
:
1. SIGHUP (Hangup signal)
- 作用:最初用于通知进程,终端连接已经断开。对于守护进程来说,接收到 SIGHUP 信号通常意味着该进程应重新加载其配置文件。
- 守护进程行为:守护进程通常会忽略 SIGHUP 信号,这样即使终端连接断开,进程也会继续运行。
2. SIGINT (Interrupt signal)
- 作用:通常由用户通过键盘操作(Ctrl+C)发送,用来中断进程的执行。
- 守护进程行为:守护进程会忽略 SIGINT 信号,避免被用户的键盘中断。
3. SIGTERM (Termination signal)
- 作用:请求进程终止的信号。系统或其他进程通常会发送此信号来请求进程优雅地结束。
- 守护进程行为:尽管守护进程有时会捕获 SIGTERM 信号并优雅地退出,但它也可能选择忽略该信号,或者采取一些特定的清理操作后继续运行。
4. SIGQUIT (Quit signal)
- 作用:通常由用户通过 Ctrl+\ 发送,用来终止进程并生成核心转储文件。
- 守护进程行为:守护进程通常会忽略 SIGQUIT 信号,以避免被意外终止并生成不必要的核心转储文件。
5. SIGCHLD (Child terminated signal)
- 作用:当子进程结束时,父进程会收到 SIGCHLD 信号,通常用于处理子进程的退出状态。
- 守护进程行为:守护进程可能会忽略 SIGCHLD 信号,特别是当它不需要对子进程的退出状态进行处理时。
6. SIGPIPE (Broken pipe signal)
- 作用:当一个进程向一个已经关闭的管道或套接字写入数据时,操作系统会发送 SIGPIPE 信号给该进程。
- 守护进程行为:守护进程通常会忽略 SIGPIPE 信号。这是因为如果进程尝试向一个已经关闭的管道或套接字写入数据,默认情况下会导致进程终止。通过忽略 SIGPIPE 信号,守护进程可以避免因意外的关闭管道而终止,通常这种情况下进程会返回一个错误代码,而不是被强制终止。
为什么守护进程忽略这些信号?
守护进程的设计目标是长时间稳定地在后台运行,因此它们通常需要避免因为用户的操作(如 Ctrl+C)、系统的请求(如终止信号)或其他不必要的信号而中断。忽略这些信号有助于确保进程不被意外终止,可以持续运行。
如何在 C/C++ 中忽略信号?
在 C/C++ 中,您可以使用 signal()
函数来捕获或忽略信号。若要忽略一个信号,可以将信号处理程序设置为 SIG_IGN
。例如:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
// 忽略 SIGHUP, SIGINT, SIGPIPE
signal(SIGHUP, SIG_IGN); // 忽略 SIGHUP
signal(SIGINT, SIG_IGN); // 忽略 SIGINT
signal(SIGPIPE, SIG_IGN); // 忽略 SIGPIPE
// 守护进程的逻辑
while (1) {
// 模拟工作
sleep(1);
}
return 0;
}
总结:
- SIGHUP: 通常用于通知终端连接断开,守护进程会忽略它。
- SIGINT: 用户通过 Ctrl+C 发送的中断信号,守护进程会忽略它。
- SIGTERM: 请求进程终止,守护进程有时会忽略它,或者捕获后进行清理操作。
- SIGQUIT: 用户通过 Ctrl+\ 发送的信号,守护进程会忽略它。
- SIGCHLD: 子进程退出时发送的信号,守护进程可能会忽略它。
- SIGPIPE: 管道或套接字关闭时发送的信号,守护进程会忽略它,避免进程被意外终止。
通过忽略这些信号,守护进程能够在后台稳定运行,避免不必要的中断或退出。
代码
先忽略几个常见信号
#pragma once
#include<iostream>
#include<cstdlib>
#include<unistd.h>
#include<signal.h>
#include<string>
using namespace std;
void Daemon(const string &cwd = "")
{
//1.忽略其他异常信号
signal(SIGCLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
//2.将自己变成独立的会话
if(fork() > 0)
{
exit(0);
}
setsid();
//3.g更改当前目录
if(!cwd.empty())
{
chdir(cwd.c_str());//更改当前目录
}
}
网络服务器以守护进程运行
/dev/null,垃圾桶
dup2重定向到/dev/null
Daemon.hpp
#pragma once
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
const string nullfile = "/dev/null";
void Daemon(const string &cwd = "")
{
// 1.忽略其他异常信号
signal(SIGCLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
// 2.将自己变成独立的会话
if (fork() > 0)
{
exit(0);
}
setsid();
// 3.g更改当前目录
if (!cwd.empty())
{
chdir(cwd.c_str()); // 更改当前目录
}
// 4.有打印的,标准输出标准输入的,所以要把表示输入,标准输出,标准错误重定向至/dev/null
// 标准错误一般要打印到日志文件,不要打印到屏幕
int fd = open(nullfile.c_str(), O_RDWR);//读写方式打开
if(fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
测试:启动后直接终止了,变成后台了
检查是否起来了netstat -nltp
ps ajx |head -1 && ps ajx |grep tcpserver
自成进程组,自成会话
查看工作目录ls /proc/644717 -l
还在当前目录
ls /proc/644717/fd -l
把xshell关闭了,服务仍然在
再打开一个xshell就等于重新启动一个会话,还能看到tcpserver进程在运行
更改目录到根目录
把服务器进程关闭掉kill -9 PID
把打印的放进日志文件
守护进程函数daemon,上面的是模拟
在 C/C++ 中,daemon()
函数用于创建守护进程(daemon)。守护进程通常是系统后台运行的进程,通常没有控制终端,并且可以在系统启动时自动启动或在用户退出登录时保持运行。守护进程会与控制终端断开连接,通常用于执行长期运行的任务。
daemon()
函数的定义
daemon()
函数通常在 <unistd.h>
中声明,原型如下:
#include <unistd.h>
int daemon(int nochdir, int noclose);
参数:
nochdir
: 如果设置为0
,守护进程将在启动时改变当前工作目录为根目录(/
)。这是因为守护进程一般不希望占用当前工作目录,并防止在程序退出时当前工作目录被锁定。如果设置为1
,则守护进程的当前工作目录不会改变。noclose
: 如果设置为0
,守护进程会关闭标准输入、标准输出和标准错误输出(stdin
,stdout
,stderr
)。通常这是守护进程的行为,以防它继续与终端交互。如果设置为1
,守护进程将不会关闭这些文件描述符。
返回值:
- 成功时,返回
0
。 - 出错时,返回
-1
,并将errno
设置为具体的错误值。
daemon()
的作用
daemon()
函数执行以下操作:
- 分离进程:它使进程脱离控制终端,成为一个守护进程。
- 改变工作目录:它将工作目录切换到根目录
/
,以确保守护进程不会阻止文件系统的卸载。 - 关闭文件描述符:它关闭进程的标准输入、标准输出和标准错误输出,通常会将这些文件描述符重定向到某个日志文件或
/dev/null
。
守护进程的常见步骤
通常,守护进程的创建步骤包括:
- 调用
fork()
创建子进程,父进程退出。 - 调用
setsid()
创建新会话并脱离终端。 - 调用
daemon()
或手动设置工作目录并关闭文件描述符。
使用 daemon()
示例
下面是一个简单的 C 程序示例,演示如何使用 daemon()
创建守护进程:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
// 调用 daemon() 创建守护进程
if (daemon(0, 0) == -1) {
perror("daemon failed");
exit(EXIT_FAILURE);
}
// 守护进程在后台执行任务
while (1) {
// 模拟后台任务
// 这里可以执行长期运行的任务,如定时备份、日志记录等
sleep(60); // 每分钟执行一次
}
return 0;
}
代码解析:
daemon(0, 0)
:将守护进程从终端脱离,改变当前工作目录到根目录,并关闭标准输入、标准输出和标准错误输出。这个调用会将进程转为守护进程。sleep(60)
:模拟守护进程在后台执行任务,每分钟执行一次。
daemon()
与 setsid()
和 fork()
的比较
在手动创建守护进程时,通常会使用 fork()
和 setsid()
来脱离终端并创建一个新的会话。然而,daemon()
函数将这些步骤封装在一个调用中,因此可以更方便地创建守护进程。基本上,daemon()
做了以下几件事:
- 创建一个子进程,父进程退出。
- 调用
setsid()
创建新会话并使进程脱离终端。 - 改变工作目录到根目录。
- 关闭标准输入、输出、错误输出。
使用守护进程时的注意事项
- 文件描述符:守护进程会关闭标准输入、标准输出和标准错误输出,因此在守护进程中通常需要将这些描述符重定向到
/dev/null
或某个日志文件。 - 退出状态:守护进程通常是长期运行的,退出时要考虑清理工作,如关闭打开的文件、释放资源等。
- 进程管理:可以使用进程管理工具如
systemd
或init.d
来启动和管理守护进程。
总结
daemon()
是一个用于创建守护进程的方便函数,它将一些常见的守护进程设置封装在一起。- 它脱离控制终端、改变工作目录为根目录、关闭标准输入输出等,使得进程成为一个后台独立运行的守护进程。
- 它是编写需要长期运行、无交互的后台任务程序时常用的函数。