目的:守护进程及相关信息写入系统日志
硬件: RPI 3B+, NORDIC nRF52840 USB Dongle
系统: Linux raspberrypi 4.19.118-v7+
说明: 参考 UNIX 高级环境编程样例实现 (UNIX 高级环境编程(第3版) 书中源码下载地址http://www.apuebook.com/src.3e.tar.gz)
文件: ttyacm-daemon-init.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <syslog.h>
#include <string.h>
#include <pthread.h>
#include <sys/resource.h>
#include <unistd.h>
#include "ttyacm-check.h"
#define LOCKFILE_TTYACM "/var/run/ttyacm-daemon.pid"
#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
static int daemon_lockfile(int fd)
{
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 0;
return( fcntl(fd, F_SETLK, &fl) );
}
static int daemon_already_running(void)
{
int fd;
char buf[16];
fd = open( LOCKFILE_TTYACM, O_RDWR|O_CREAT, LOCKMODE );
if ( fd < 0 )
{
//syslog( LOG_ERR, "can't open %s: %s", LOCKFILE_TTYACM, strerror(errno) );
return -1;
}
if ( daemon_lockfile(fd) < 0 )
{
if ( errno == EACCES || errno == EAGAIN ) {
close(fd);
return -1;
}
// syslog(LOG_ERR, "can't lock %s: %s", LOCKFILE_TTYACM, strerror(errno));
return -1;
}
if ( ftruncate(fd,0) < 0)
return -1;
if ( sprintf(buf, "%ld", (long)getpid()) <= 0 )
return -1;
if ( write(fd, buf, strlen(buf)+1) < 0 )
return -1;
return(0);
}
static int thread_ttyacm_check(void)
{
pthread_t tid;
int err = pthread_create( &tid,NULL,ttyacm_check,NULL );
if ( err )
return -1;
pthread_join( tid,NULL );
return 0;
}
int ttyacm_daemon_init(void)
{
int pid;
struct sigaction sa;
/* Clear file creation mask */
umask(0);
/* Become a session leader to lose controlling TTY */
if ( (pid=fork()) < 0 ) // fork error
return -1;
else if ( pid != 0 ) // parent process
{
closelog();
exit(0);
}
/* new session, child process */
setsid();
/* Ensure future opens won't allocate controlling TTYs. */
sa.sa_handler = SIG_IGN;
sigemptyset( &sa.sa_mask );
sa.sa_flags = 0;
if ( sigaction(SIGHUP, &sa, NULL) < 0 )
return -1;
/*
* second fork, rebuild a child process and quit its parent process,
* to avoid to open a new terminal again
*/
if ( (pid=fork()) < 0 ) // fork error
return -1;
else if ( pid != 0 )
{
closelog();
exit(0);
}
#if 0
/* Get maximum number of file descriptors */
struct rlimit rl;
if ( getrlimit(RLIMIT_NOFILE,&rl) < 0 )
return -1;
if ( rl.rlim_max == RLIM_INFINITY )
rl.rlim_max = 1024;
int i;
for ( i=0; i<rl.rlim_max; i++ )
{
if ( close(i) < 0 )
return -1;
}
#endif
// close std file descriptions from parent process
int fd = open( "/dev/null",O_RDWR );
if ( fd < 0 )
return -1;
dup2( fd,0 );
dup2( fd,1 );
dup2( fd,2 );
if ( fd > 2 )
{
if ( close(fd) < 0 )
return -1;
}
// change work directory to root to cut the connection of file system
chdir( "/" );
#if 0
if ( daemon(0,0) ) // daemon() equals close() + chdir()
{
syslog( LOG_ERR,"close(fd) or chdir() Err,%s.",strerror(errno) );
return -1;
}
#endif
if ( daemon_already_running() )
{
return -1;
}
syslog( LOG_INFO,"TTYACM-DAEMON-INIT SUCCESS!" );
/* call ttyacm-operation-pthread */
if ( thread_ttyacm_check() )
return -1;
return 0;
}
单守护进程相关要点:
1. 调用 unmask(0) 将文件创建屏蔽字清零;
2. 让程序在后台执行; 调用 fork() 产生一个子进程,然后使父进程退出;
3. 调用 setsid() 创建一个新会话期; 子进程成为新的会话组长和进程组长,并与原来的登录会话、进程组和控制终端脱离;
4. 屏蔽所有 signal 信号影响;
5. 禁止进程重新打开控制终端; 再一次调用 fork() 创建新的子进程,并使第一次调用 fork() 产生的子进程退出;
6. 关闭不再需要的文件描述符;
7. 标准输入输入 stdin,stdout,stderr 批向 /dev/null;
8. 将当前目录更改为根目录;
单守护进程锁文件
此文件存储于 /var/run 目录, 通常全名为 name.pid, name 是对应的守护进程或服务名字, 文件为 root / root 权限. 文件内容是: 对应守护进程的 pid .
守护进程配置文件
此文件存储于 /etc 目录, 通常全名为 name.conf, name 是对应的守护进程或服务名字, 文件为 root / root 权限.
* 于守护进程中,添加程序捕捉 SIGHUP 信号,可让程序重读配置文件,使配置文件更改生效。d
守护进程启动
* 守护进程可由命令行 shell 启动;
* 通常由系统初始化脚本启动; ( /etc/rc* 或 /etc/init.d/* )
比如: 于 rc.local 文件中,添加 /home/pi/ttyacm-daemon, 则开机启动守护进程;
* 守护进程终止时,如需系统可自动重新启动它,则 在 /etc/inittab 中为该守护进程添加 repawn 记录j ;
查看系统日志
$ tail -F /var/log/syslog
$ journalctl -xe
查看系统守护进程
$ ps -axj | grep -v grep | grep ttyacm
查看进程中线程父子关系
$ pstree -a