------------->【Linux系统编程/网络编程】(学习目录汇总) <--------------
目录
1.守护进程
守护进程(Daemon Process),也就是通常说的Daemon(精灵)进程,是 Linux 中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理 某些发生的事件。一般采用以 d 结尾的名字。
Linux 后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp 服务器;nfs 服务器等。
创建守护进程,最关键的一步是调用 setsid
函数创建一个新的 Session,并成为 Session Leader。
1.1 进程组
多个进程的集合就是进程组,也称之为作业。BSD 于 1980 年前后向 Unix 中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在waitpid
函数和 kill
函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了简化对多个进程的管理。
当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组 ID == 第一个进程 ID(组长进程)。 所以,组长进程标识:其进程组 ID == 其进程 ID
可以使用 kill -SIGKILL -进程组 ID(负的)
来将整个进程组内的进程全部杀死。
组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。
进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。
一个进程可以为自己或子进程设置进程组 ID
下面介绍几个常用的进程组函数:
得到当前进程所在的进程组的组 ID
pid_t getpgrp(void);
获取指定的进程所在的进程组的组 ID,参数 pid 就是指定的进程
pid_t getpgid(pid_t pid);
将某个进程移动到其他进程组中或者创建新的进程组
int setpgid(pid_t pid, pid_t pgid);
函数参数:
pid:
某个进程的进程 ID
pgid:
某个进程组的组 ID
如果 pgid 对应的进程组存在,pid 对应的进程会移动到这个组中,pid != pgid
如果 pgid 对应的进程组不存在,会创建一个新的进程组,因此要求 pid == pgid, 当前进程就是组长了
函数返回值:
函数调用成功返回 0,失败返回 - 1
1.2 会话
会话 (session) 是由一个或多个进程组组成的,一个会话可以对应一个控制终端,也可以没有。一个普通的进程可以调用 setsid()
函数使自己成为新 session 的领头进程(会长),并且这个 session 领头进程还会被放入到一个新的进程组中。先来看一下 setsid() 函数的原型:
1.3 setsid()函数
创建一个会话,并以自己的 ID 设置进程组 ID,同时也是新会话的 ID。
函数原型:
#include <unistd.h>
pid_t setsid(void);
函数返回值:
- 成功:返回调用进程的会话 ID
- 失败:-1,设置 errno
1.4 getsid()函数
获取进程所属的会话 ID
函数原型
#include <unistd.h>
pid_t getsid(pid_t pid);
函数返回值:
- 成功:返回调用进程的会话 ID
- 失败:-1,设置 errno
pid 为 0 表示察看当前进程 session ID
使用这两个函数的注意事项:
1.调用这个函数的进程不能是组长进程,如果是,该函数调用失败,如何保证这个函数能调用成功呢?
- 先 fork () 创建子进程,终止父进程,让子进程调用这个函数
2.如果调用这个函数的进程不是进程组长,会话创建成功
- 这个进程会变成当前会话中的第一个进程,同时也会变成新的进程组的组长
- 该函数调用成功之后,当前进程就脱离了控制终端,因此不会阻塞终端
1.5 创建守护进程
- 创建子进程,父进程退出
- 在子进程中创建新会话
setsid()函数
使子进程完全独立出来,脱离控制 - 改变当前目录位置
chdir()函数
防止占用可卸载的文件系统, 也可以换成其它路径 - 重设文件权限掩码
umask()
函数 防止继承的文件创建屏蔽字拒绝某些权限,增加守护进程灵活性 - 通常根据需要,关闭/重定向 文件描述符
- 开始执行守护进程业务逻辑,通常是while()循环
小案例
写一个守护进程,每隔 2s 获取一次系统时间,并将得到的时间写入到磁盘文件中。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <time.h>
// 信号的处理动作
void writeFile(int num)
{
// 得到系统时间
time_t seconds = time(NULL);
// 时间转换, 总秒数 -> 可以识别的时间字符串
struct tm* loc = localtime(&seconds);
// sprintf();
char* curtime = asctime(loc); // 自带换行
// 打开一个文件, 如果文件不存在, 就创建, 文件需要有追加属性
// ./对应的是哪个目录? /home/robin
// 0664 & ~022
int fd = open("./time+++++++.log", O_WRONLY|O_CREAT|O_APPEND, 0664);
write(fd, curtime, strlen(curtime));
close(fd);
}
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main()
{
int ret;
// 1. 创建子进程, 杀死父进程
pid_t pid = fork();
if(pid > 0)
{
// 父进程终止
exit(0); // kill(getpid(), 9); raise(9); abort();
}
// 2. 子进程, 将其变成会话, 脱离当前终端
pid = setsid();
if(pid == -1)
{
sys_err("setsid error\n");
}
// 3. 修改进程的工作目录, 修改到一个不能被修改和删除的目录中 /home/robin
ret = chdir("/home/robin");
if(ret == -1)
{
sys_err("chdir error\n")
}
// 4. 设置掩码, 在进程中创建文件的时候这个掩码就起作用了
umask(022);
// 5. 重定向和终端关联的文件描述符 -> /dev/null
int fd = open("/dev/null", O_RDWR);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
// 5. 委托内核捕捉并处理将来发生的信号-SIGALRM(14)
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = writeFile;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, NULL);
// 6. 设置定时器
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(100);
}
return 0