Linux系统编程-会话、守护进程与系统日志

目录

[一. 会话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 创建会话)

1、注意事项:

2、setsid()函数:

[二. 守护进程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点注意事项:

  1. 调用进程不能是进程组组长 ,该进程变成新会话首进程(会长)(session header)
  2. 该进程成为一个新进程组的组长进程
  3. 需有root权限 (ubuntu不需要)
  4. 新会话丢弃原有的控制终端,该会话没有控制终端,不可与用户交互,后台执行。
  5. 该调用进程是组长进程,则出错返回
  6. 建立新会话时,先调用fork, 父进程终止,子进程调用setsid
  7. 组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
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查看日志文件:

相关推荐
赵民勇2 小时前
Linux strings命令详解
linux·运维
GongzZz2 小时前
Linux 内存分配差异:用户空间 vs 内核空间
linux
敲代码的瓦龙2 小时前
操作系统?Android与Linux!!!
android·linux·运维
xiaoye-duck2 小时前
《Linux系统编程》Linux 进程信号深度解析(上):信号的产生方式、本质和闹钟
linux
Dxy12393102162 小时前
BAT 窗口不输出日志:三种静默方案,从半隐藏到完全消失
linux·运维·服务器
Tian_Hang3 小时前
Linux基础知识(一)
linux·运维·服务器
行智科技3 小时前
ORB-SLAM3代码详解 - 第 01 篇 · 系统总览与三线程架构
linux·ubuntu·架构·自动驾驶
fishwww_ww4 小时前
服务器免密登录与流量端口转发
linux
开开心心_Every5 小时前
解决打印机共享难题的实用工具
linux·b树·安全·游戏·随机森林·pdf·计算机外设