目录
进程关系
-
会话session
- 打开一个终端就是创建一个会话
- 会话就是一个容器,承载进程组
- 会话标识 SID == 第一个进程组的ID
- 获取会话ID:getsid(2);
- 设置会话:setsid(2);
-
进程组 process group
- 进程组也是一个容器
- 承载进程的
- 进程组的标识 PGID == 进程组内第一个进程的PID
- 前台进程组和后台进程组
- 一个会话内可以由一个前台进程组和若干个后台进程组
- 前台进程组就是容纳交互进程的
- 获取:getpgid(2); / getpgrp(2);
- 设置:setpgid(2);
-
进程process
- 也是容器,承载线程的
-
进程组组长
- 会话内第一个进程组内的第一个进程就是所在进程组的组长也是所在会话的组长。
- 组长的PID == PGID == SID
- 会话内第一个进程组内的第一个进程就是所在进程组的组长也是所在会话的组长。
-
创建会话 setsid(2)
- 功能是创建一个新的会话,前提是非组长进程调用
- 调用进程将为称为新的会话内,新的进程组内唯一的进程
- SID == PID == PGID
- 脱离控制终端
-
交互进程和守护进程
- 交互进程是占用终端的进程,可以在终端交互的
- 守护进程就是脱离控制终端的,并且这个进程是所在进程组组长,所在会话组长。
-
如何构建守护进程
- fork(2)子进程,父进程终止
- 子进程调用setsid(2)
- PID == PGID == SID
- 不占用控制终端
- 0, 1, 2对应标准输入标准输出和标准错误输出文件,这三个文件都是与终端交互的,所以守护进程应该重定向0, 1, 2,建议重定向到"/dev/null"
- 每一个进程都有文件屏蔽字,如果0这个进程占用终端,那么我们在终端通过umask命令是可以修改bash的文件屏蔽字的,进而在终端下执行的进程也就是bash的子进程也就随着修改了文件屏蔽字。
- 守护进程不占用终端,所以文件屏蔽字就不能修改了
- 建议将守护进程的文件屏蔽字提前设置为0
- 每个进程都有工作目录,守护进程不占用终端,建议将工作目录切换到"/",chdir(3)
-
构建守护进程函数 daemon(3)
- int daemon(int nochdir, int noclose);
- nochdir是0则将进程的工作目录切换到/
- noclose是0则将0, 1, 2重定向到/dev/null
- int daemon(int nochdir, int noclose);
-
程序写日志
- 日志
- openlog(3)
- syslog(3)
- closelog(3)
- linux中有一个专门写日志的进程system logger
- syslog提交给system logger,默认写"/var/log/syslog"
- 日志
-
单实例守护进程
- 同时只能执行一次
- flock(2)给文件加锁,锁的类型有共享锁,互斥锁(同时只能一个进程得到)
-
守护进程实例
// Date:2023.08.30 16:10:56
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <sys/file.h>
#define FLNAME "/tmp/out" // 写入时间的文件
#define DEVNULL "/dev/null"
#define RUNDAEMON "/var/run/daemon.pid" // 写入id的文件 [sudo]
#define FILENAME "03protect" // 当前文件名
#define BUFFSIZE 128
/* 守护进程 例[写入时间到文件 转后台] */
char *get_time(char *buf);
int mydaemon(void);
int alread_running(void);
int main(void)
{
int i = 0;
char buf[BUFFSIZE] = {};
FILE *fp = NULL;
// 创建连接
openlog(FILENAME, LOG_PID, LOG_DAEMON);
// 守护进程
if(-1 == mydaemon())
exit(1);
if(NULL == (fp = fopen(FLNAME, "w"))){
//perror("fopen()");
// 错误输出到日志
syslog(LOG_ERR, "[%d]fopen():%s", __LINE__, strerror(errno));
return 1;
}
// 单实例
if(-1 == alread_running()){
syslog(LOG_ERR, "the process is already running");
exit(1);
}
while(1){
/* 程序正常运行日志 [Debug] */
if(++i == 5){
syslog(LOG_INFO, "%s is running", buf);
i = 0;
}
fputs(get_time(buf), fp);
fputs("\n", fp);
fflush(NULL); // 全缓存, 需要刷新
}
return 0;
}
char *get_time(char *a)
{
char *buf = a;
time_t tn;
struct tm *tim = NULL;
time(&tn);
memset(buf, '\0', BUFFSIZE);
if(NULL == (tim = localtime(&tn)))
return NULL;
strftime(buf, BUFFSIZE, "%y-%m-%d %H:%M:%S", tim);
sleep(1);
return a;
}
// 守护进程---》精灵进程 daemon
int mydaemon(void)
{
pid_t pid;
int fd;
/* 创建子进程 */
if(-1 == (pid = fork())){
perror("fork()");
return -1;
}
/* 关闭父进程 */
if(pid > 0)
exit(0);
/* child 作为会话 */
if(-1 == setsid()){
perror("setsid()");
return -1;
}
// PID == PGID PID == SID 脱落控制终端
umask(0); // 给予更多权限
chdir("/");// 将当前目录 --> 根目录
// 0 1 2 重定向[关闭不需要的文件描述符]
if(-1 == (fd = open(DEVNULL, O_RDWR))){
perror("open()");
return -1;
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if(fd > 2)
close(fd);
return 0;
}
// 单实例 [加锁,只执行一次]
int alread_running(void)
{
int fd;
char buf[BUFFSIZE] = {};
// 在/run文件下产生一个pid文件,供管理人员查看id号
if(-1 == (fd = (open(RUNDAEMON, O_WRONLY | O_CREAT, 0666)))){
syslog(LOG_ERR, "open():%s", strerror(errno));
return -1;
}
// 加锁
if(-1 == flock(fd, LOCK_EX | LOCK_NB)){
syslog(LOG_ERR, "flock():%s", strerror(errno));
return -1;
}
// 调用进程目前还没执行过
snprintf(buf, BUFFSIZE, "%d", getpid());
// 清空原文件内容
ftruncate(fd, 0);
write(fd, buf, strlen(buf));
// 文件不要要关闭,否则会影响锁
return 0;
}
- ps -axj | tail