守护进程是什么?
虽然这个问题在前两篇文章中我们已经谈论过很多次了,但是在这篇文章中我要更加清晰详细地来说这个事情:
守护进程(Daemon)也被翻译为精灵进程、是一种旨在运行于相对干净环境、不受终端影响的、常驻内存的进程,就像神话中的精灵拥有不死的特性,长期稳定提供某种功能或服务(当然你要是把电脑电给断掉之后,守护进程也会被杀死哈哈)电脑上常见的守护进程有:系统日志进程,网络管理进程等(这些进程一般来说都伴随着我们从开机到关机,我们需要它们来给我们提供长期稳定的服务)。
查看守护进程
在Unix/Linux系统中,使用 ps 命令可以看到许多以 -d 结尾的进程(Daemon),它们大多都是守护进程,例如:
ps -ef | grep '.*d$'
'.*d$' 是一个正则表达式,用于匹配以 "d" 结尾的字符串。其中 .* 是一个非常常见且强大的组合,它表示匹配任意字符(除了换行符)零次或多次。d$ 表示匹配以 "d" 结尾的字符串。
守护进程完整创建过程
守护进程在创建过程很固定,就是这样的一个套路,如果你不知理解某些步骤的话其实也没有关系,到时候把这里的固定套路模板复制粘贴即可。
1.创建子进程并让父进程退出:
首先,通常会使用 fork()
创建一个子进程。父进程退出,确保守护进程不再与终端连接,避免被终端控制。
// 创建子进程
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
} else if (pid > 0) {
// 父进程退出
exit(0);
}
2. 在子进程中创建新的会话
// 创建新的会话
if (setsid() == -1) {
perror("setsid failed");
exit(1);
}
使用 setsid() 创建一个新的会话,并使当前进程成为会话领导进程。这样,进程就会脱离控制终端,不再与终端或控制台相连。这样做的目的是防止该进程将来收到终端的信号(例如 SIGHUP)。
3. 改变子进程当前工作目录
守护进程通常会将工作目录更改为根目录(/
)。这样做的目的是防止守护进程占用任何特定的文件系统目录,避免出现不小心挂载文件系统时工作目录被占用的情况。
如果守护进程保持其工作目录指向某个特定目录,假设该目录所在的文件系统被卸载(或重新挂载),则守护进程可能会因为工作目录丢失而崩溃或出现错误。工作目录设置成根目录就不需要担心这个问题了。
// 改变工作目录
if (chdir("/") == -1) {
perror("chdir failed");
exit(1);
}
4. 设置文件权限掩码
使用 umask()
设置文件权限掩码,通常将其设置为 0
,这样守护进程创建的文件权限不会受到权限掩码的限制。创建的文件的权限就等于默认的权限(touch 666,mkdir 777)
// 设置文件权限掩码
umask(0);
关于文件权限掩码的详细介绍在本文后面专门有详细介绍
5. 关闭文件描述符
- 关闭所有不需要的文件描述符,包括标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)。
- 这样做的好处是:
- 避免资源浪费: 释放不再使用的文件描述符,节省系统资源。
- 防止意外输出: 避免守护进程的输出干扰终端或其他程序。
6.进入守护进程的主循环
守护进程进入一个无限循环,执行需要完成的任务。在主循环中,可以根据需要处理各种信号、事件或定时任务。
守护进程创建完整代码框架
int main() {
pid_t pid;
// 1. 创建子进程,父进程退出
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
} else if (pid > 0) {
// 父进程退出
exit(0);
}
// 2. 创建新的会话
if (setsid() == -1) {
perror("setsid failed");
exit(1);
}
// 3. 改变当前工作目录
if (chdir("/") == -1) {
perror("chdir failed");
exit(1);
}
// 4. 设置文件权限掩码
umask(0);
// 5. 关闭文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 6. 守护进程的主循环,在这里反复监听
daemon_main();
return 0;
}
守护进程在创建过程很固定,就是这样的一个套路,如果你不知理解某些步骤的话其实也没有关系,到时候把这里的固定套路模板复制粘贴即可。
文件权限掩码
回顾文件权限码
Linux系统中有三种基本权限:
- 读取(r): 允许查看文件内容。
- 写入(w): 允许修改文件内容。
- 执行(x): 允许运行文件(如果是可执行文件)。
每个文件或目录都有三组权限:
- 所有者(Owner): 拥有文件的人。
- 所属组(Group): 文件所属的组。
- 其他用户(Others): 除所有者和所属组之外的所有用户。
权限码可以用符号或数字表示。
符号表示法
用 r
、w
、x
和 -
分别表示读取、写入、执行和无权限。例如:
rw-r--r--
表示文件所有者有读写权限,所属组有读权限,其他用户有读权限。
数字表示法
用数字 4、2、1 和 0 分别表示读取、写入、执行和无权限。然后将每组权限的数字相加,得到一个三位数的权限码。例如:
644
表示文件所有者有读写权限(4+2=6),所属组有读权限(4),其他用户有读权限(4)。
可以使用 chmod 命令修改文件或目录的权限。例如:
chmod 755 myfile 将 myfile 的权限设置为所有者有读写执行权限,所属组和其他用户有读执行权限。
默认文件权限
当我们使用mkdir命令创建文件目录和touch命令创建文件的时候,并不能直接指定文件的权限。都是根据文件的默认权限来设定的。
默认权限
- 文件: 默认权限通常是
666
(rw-rw-rw-),表示所有用户都具有读写权限。 - 目录: 默认权限通常是
777
(rwxrwxrwx),表示所有用户都具有读写执行权限。
但是你使用touch创建文件以及使用mkdir创建目录文件后,ls -l查看发现文件权限并不是666和777,这是因为有权限掩码的存在。
注意:默认的文件和目录权限(如 0666
和 0777
)是由操作系统设定的,不能修改!!!!!这些默认权限是文件系统为新创建的文件和目录预定义的。
文件权限掩码(umask)是什么
权限掩码通常用一个三位八进制数表示。每一位数字的取值范围是0-7,每一位数字对应一类用户:
- 第一位: 文件所有者(Owner)
- 第二位: 所属组(Group)
- 第三位: 其他用户(Others)
新创建的文件或目录的实际权限由默认权限和权限掩码共同决定。
- 默认权限: 系统对不同类型的文件和目录有不同的默认权限设置。例如,文件默认权限通常是
666
(rw-rw-rw-),目录默认权限通常是777
(rwxrwxrwx)。 - 权限掩码: 权限掩码中每一位为1的表示要从默认权限中去除相应的权限。
实际权限 = 默认权限 - 权限掩码
举例说明实际权限 = 默认权限 - 权限掩码:
假设权限掩码为
022
,新创建的文件默认权限为666
。
- 文件所有者:6 - 0 = 6 (rw-)
- 所属组:6 - 2 = 4 (r--)
- 其他用户:6 - 2 = 4 (r--)
因此,新创建文件的实际权限为
644
(rw-r--r--)。
权限掩码是Linux系统中一个重要的安全机制,它可以帮助我们设置更安全的默认权限,防止新创建的文件或目录默认拥有过高的权限。
无论你如何创建文件或目录,操作系统都会根据当前的 umask 和默认权限来计算最终的权限。但是请注意:
umask
只影响新创建的文件和目录的权限。chmod
修改已经存在文件或目录的权限时,直接修改权限,不会受umask
的影响。
文件权限掩码的查看与设置
终端中查看与设置文件权限掩码:
查看当前的 umask:
umask
设置 umask:
umask 022
程序中设置文件权限掩码
umask()
函数用于在程序中设置进程的权限掩码。
函数原型:mode_t umask(mode_t mask);
文件权限掩码的特点
umask
是进程级别的:
umask
的设置是与当前进程相关的,而不是与文件系统的某个目录关联的。- 一旦你在一个进程中设置了
umask
,该进程及其派生的子进程(包括通过fork()
或其他方式创建的子进程)会继承该umask
值,直到进程修改它。 - 不同的进程可以拥有不同的
umask
设置。 - 子进程可以改变自己的
umask
,并且这种改变只会影响子进程及其后代进程。 - 在终端中运行某个命令或启动其他程序时,这些命令或程序会继承当前 shell 进程的
umask
设置。