目录
[一. 会话session](#一. 会话session)
[1.1 进程组](#1.1 进程组)
[1.1.1 进程组](#1.1.1 进程组)
[1.1.2 getpgid()](#1.1.2 getpgid())
[1.2 会话](#1.2 会话)
[1.2.1 概念](#1.2.1 概念)
[1.2.2 getsid()](#1.2.2 getsid())
[1.2.3 创建会话](#1.2.3 创建会话)
[二. 守护进程daemon](#二. 守护进程daemon)
[2.1 概念](#2.1 概念)
[2.2 守护进程的创建](#2.2 守护进程的创建)
[2.3 用到的函数](#2.3 用到的函数)
[2.3.1 chdir](#2.3.1 chdir)
[2.3.2 umask](#2.3.2 umask)
[2.4 示例代码1](#2.4 示例代码1)
[2.5 示例代码2](#2.5 示例代码2)
[三. 系统日志](#三. 系统日志)
[3.1 系统日志](#3.1 系统日志)
[3.2 三个函数](#3.2 三个函数)
[3.2.1 openlog](#3.2.1 openlog)
[3.2.2 syslog](#3.2.2 syslog)
[3.2.3 closelog](#3.2.3 closelog)
[3.3 修改后的示例代码2](#3.3 修改后的示例代码2)
一. 会话session
1.1 进程组
1.1.1 进程组
1、进程组 ,也称之为作业 。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合 。每个进程都属于一个进程组。eg:当父进程fork出子进程时,这个子进程就和父进程属于同一进程组,他们的进程组ID(PGID )相同,都是父进程的PID,父进程就是组长进程。
当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组ID==第一个进程ID(组长进程)。所以,组长进程标识:其进程组ID==其进程ID
示例代码:
cpp
int main()
{
pid_t pid;
pid = fork();
if(pid > 0){
wait(NULL);
}
else if(pid == 0){
while(1){
printf("===child===\n");
sleep(1);
}
}
exit(0);
}
结果:可以看到,子进程与父进程同属于一个进程组

2、在waitpid函数 和kill函数 的参数中都曾使用到。操作系统设计的进程组的概念,是为了简化对多个进程的管理。
waitpid函数中第一个参数传0,代表等待与调用者同属于同一进程组的任何进程。
kill函数中第一个参数传0,代表发生信号给与调用者同属于同一进程组的所有进程
同时,可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死。
3、组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。
4、**进程组生存期:**进程组创建到最后一个进程离开(终止或转移到另一个进程组)。
一个进程可以为自己或子进程设置进程组ID
1.1.2 getpgid()

**作用:**获取指定进程的进程组ID,即PGID
**参数:**pid:指定进程,若传入0,则返回调用者的进程组ID
**返回值:**成功返回进程组ID,失败返回-1并且设置errno
1.2 会话
1.2.1 概念
**会话:**多个进程组的集合

1.2.2 getsid()

**作用:**返回指定进程的会话ID
**参数:**pid:指定进程 如果pid传入0,则返回调用者的会话ID(SID)
**返回值:**成功返回会话ID 失败返回-1且设置errno
1.2.3 创建会话
1、注意事项:
创建一个会话需要注意以下6点注意事项:
- 调用进程不能是进程组组长 ,该进程变成新会话首进程(会长)(session header)
- 该进程成为一个新进程组的组长进程。
- 需有root权限 (ubuntu不需要)
- 新会话丢弃原有的控制终端,该会话没有控制终端,不可与用户交互,后台执行。
- 该调用进程是组长进程,则出错返回
- 建立新会话时,先调用fork, 父进程终止,子进程调用setsid
- 组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
2、setsid()函数:

**作用:**setsid() 函数会在调用进程不是进程组组长的情况下创建一个新的会话。调用进程将成为新
会话的组长(会话ID=进程I D)。该调用进程还会成为该会话中新进程组的组长(进程组ID=
进程ID)
**参数:**无
**返回值:**成功:将返回调用进程的新会话ID 失败返回-1且设置errno
示例代码:
cpp
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
sys_err("fork()");
}
else if(pid == 0){
/子进程创建会话前打印进程id,组id,会话id
printf("child pid is %d\n",getpid());
printf("child gpid is %d\n",getpgid(0));
printf("child sid is %d\n",getsid(0));
sleep(2);
pid_t sid = setsid();/子进程非组长,调用setsid成功创建会话,并且
/创建之后子进程成为进程组组长,会话的会长,pid=pgid=sid
if(sid < 0){
sys_err("setsid()");
}
printf("sid is %d\n",sid);
printf("child pid is %d\n",getpid());
printf("child gpid is %d\n",getpgid(0));
printf("child sid is %d\n",getsid(0));
sleep(20);/这段时间可查看进程信息
}
exit(0);
}
结果:

查看进程信息:


二. 守护进程daemon
2.1 概念
1、Daemon (精灵)进程,是Linux中的后台 服务进程,通常独立于控制终端 并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字(httpd,sshd,vsftpd,nfsd)。
tty:文字终端 pts:虚拟终端
2、Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp服务器;nfs服务器等。
3、创建守护进程,最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。
4、 使用kill命令可以结束它,或者使用脚本文件去管理它
2.2 守护进程的创建
1、创建子进程,父进程退出
所有工作在子进程中进行形式上脱离了控制终端
2、在子进程中创建新会话
setsid()函数
使子进程完全独立出来,脱离控制
3、改变当前目录为根目录,或者家目录也可以
chdir()函数
防止占用可卸载的文件系统
也可以换成其它路径
使用命令pwdx+pid:查看进程运行在哪个路径
4、重设文件权限掩码
umask()函数
防止继承的文件创建屏蔽字拒绝某些权限
增加守护进程灵活性,守护进程有时候需要创建系统日志文件等
5、关闭文件描述符
继承的打开文件不会用到,浪费系统资源,无法卸载
一般将0,1,2号文件描述符重定向到**/dev/null** 这个空洞文件中
6、开始执行守护进程核心工作守护进程退出处理程序模型
**注意:**由于守护进程不受用户的登录注销影响,最后要结束守护进程只能采用kill命令将其结束
2.3 用到的函数
2.3.1 chdir

**作用:**改变调用进程的工作路径
**参数:**指定的工作路径
**返回值:**成功返回0 失败返回-1且设置errno
2.3.2 umask

**作用:**改变当前进程的掩码
**参数:**mask要修改的值,一个八进制数
**返回值:**此函数总是成功
注意: 系统默认:创建文件最大默认权限:0666 ,创建目录最大默认权限:0777
2.4 示例代码1
cpp
int main()
{
pid_t pid;
int ret;
pid = fork();
if(pid < 0){
sys_err("fork()");
}
else if(pid > 0){/父进程退出
exit(0);
}
else{
pid_t sid = setsid();/子进程创建会话,脱离终端
if(sid < 0){
sys_err("setsid()");
}
ret = chdir("/");/改变工作目录,防止占用可卸载的文件系统
if(ret < 0){
sys_err("chdir()");
}
umask(0022);/防止继承父进程的掩码创建的文件权限过紧或者过松
/守护进程一般要创建日志文件
int fd = open("/dev/null", O_RDWR);/空洞文件
if(fd < 0){
sys_err("open()");
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);/将文件描述符0,1,2重定向,守护进程脱离终端
if(fd > 2){/防止fd原本就是0,1,2中的其中一个
close(fd);
}
while(1);/守护进程逻辑
}
exit(0);
}
结果:


2.5 示例代码2
cpp
static int my_daemon()
{
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork()");
return -1;
}
else if(pid > 0){
exit(0);
}
else{
pid_t sid = setsid();
if(sid < 0){
perror("setsid()");
return -1;
}
chdir("/");
umask(0022);
int fd = open("/dev/null", O_RDWR);
if(fd < 0){
perror("open()");
return -1;
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if(fd > 2){
close(fd);
}
return 0;
}
}
int main()
{
int ret = my_daemon();
if(ret == -1){
exit(1);
}
FILE *fp = fopen("/tmp/out", "w");
if(fp == NULL){
perror("fopen()");
exit(1);
}
for(int i = 0;;i++){
fprintf(fp,"%d\n",i);
fflush(fp);/文件为全缓冲,需要强制刷新
sleep(1);
}
exit(0);
}
结果:



三. 系统日志
3.1 系统日志
1、在示例代码2中,创建了守护进程之后,往**/tmp/out** 文件写东西来模拟守护进程的逻辑,但是也可能出现打开这个文件失败的情况,打开失败后,使用perror来在终端上进行报错打印,但是终端已经被重定向,这时,就需要向系统日志中来写报错信息。程序 / 服务器脱离终端,必须靠日志输出信息
2、注意:只有syslogd服务才有权限写系统日志(为了权限分割,不可能每个程序都有权限写系统日志)
3、系统日志在/var/log下,全部是系统日志,主日志文件:messages,ubuntu是在syslog中 ,可以使用命令tail /var/log/syslog来查看日志文件
3.2 三个函数
3.2.1 openlog

**作用:**用于为程序打开与系统日志记录器的连接
参数:
|------------------|---------------------|----------------------------------------------------------------|
| ident--字段 | 程序名 NULL | 由 ident 指向的字符串会被添加到每条消息的开头,并且通常会被设置为程序名。如果 ident 为 NULL,则使用程序名 |
| option--选项 | LOG_PID | 日志中带上pid |
| facility--来源 | LOG_USER LOG_DAEMON | 用户进程 守护进程 |
**返回值:**无
3.2.2 syslog
**作用:**syslog() 会生成一条日志消息,该消息将由 syslogd 进行分发
参数:
|------------------|--------------------------------------------------------------------------------|---------------------------------------------------------------------|
| priority--级别 | LOG_EMERG LOG_ALERT LOG_CRIT LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_DEBUG | 崩溃 需立即处理 严重错误 错误 警告 注意 普通信息 调试信息 |
| format | - | format:内容,类似printf,注意不用加\n等来控制格式,格式是由syslogd服务来控制的,只传入要提交的字符即可 |
**返回值:**无
3.2.3 closelog

**作用:**关闭用于向系统日志写入数据的文件描述符。
**参数:**无
**返回值:**无
3.3 修改后的示例代码2
cpp
#define FNAME "/tmp/out"
static int my_daemon()
{
pid_t pid;
pid = fork();
if(pid < 0){
return -1;
}
else if(pid > 0){
exit(0);
}
else{
pid_t sid = setsid();
if(sid < 0){
return -1;
}
chdir("/");
umask(0022);
int fd = open("/dev/null", O_RDWR);
if(fd < 0){
return -1;
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if(fd > 2){
close(fd);
}
return 0;
}
}
int main()
{
openlog("my_daemon", LOG_PID, LOG_DAEMON);
int ret = my_daemon();
if(ret == -1){
syslog(LOG_ERR,"my_daemon() failed!");/报错,写在系统日志中
exit(1);
}
syslog(LOG_INFO, "my_daemon() successded!");
FILE *fp = fopen(FNAME, "w");
if(fp == NULL){
syslog(LOG_ERR, "fopen:%s",strerror(errno));
exit(1);
}
syslog(LOG_INFO, "%s was opened",FNAME);
for(int i = 0;;i++){
fprintf(fp,"%d\n",i);
fflush(fp);
sleep(1);
}
fclose(fp);
closelog();
exit(0);
}
结果:

使用命令tail /var/log/syslog查看日志文件:

