Linux高性能服务器编程 学习笔记 第七章 Linux服务器程序规范

news2024/10/5 17:20:46

除了网络通信外,服务器程序通常还需考虑许多其他细节问题,这些细节问题涉及广而零碎,且基本上是模板式的,我们称之为服务器程序规范,如:
1.Linux服务器程序一般以后台进程形式运行,后台进程又称守护进程(daemon),它没有控制终端,因此不会意外接收到用户输入。守护进程的父进程通常是init进程(PID为1的进程)。

2.Linux服务器程序通常有一套日志系统,它至少能输出日志到文件,有的高级服务器还能输出日志到专门的UDP服务器。大部分后台进程都在/var/log目录下拥有自己的日志目录。

3.Linux服务器程序一般以某个专门的非root身份运行,如mysqld、httpd、syslogd等后台进程分别拥有运行账户mysql、apache、syslog。

4.Linux服务器程序通常是可配置的,服务器程序通常能处理很多选项,如果选项太多,除命令行外可用配置文件来管理。绝大多数服务器程序都有配置文件,并存放在/etc目录下,如squid服务器的配置文件是/etc/squid3/squid.conf。

5.Linux服务器进程通常会在启动时生成一个PID文件并存入/var/run目录中,以记录该后台进程的PID,如syslogd的PID文件是/var/run/syslogd.pid。

6.Linux服务器通常需要考虑系统资源和限制,以预测自身能承受多大负荷,如进程可用文件描述符总数和内存总量等。

服务器的调试和维护都需要一个专业的日志系统,Linux提供一个守护进程来处理系统日志,即syslogd,但现在Linux上使用的都是它的升级版rsyslogd。

rsyslogd守护进程既能接收用户进程输出的日志,又能接收内核日志。用户进程是通过调用syslog函数生成日志的,该函数将日志输出到一个UNIX本地域socket类型(AF_UNIX)的文件/dev/log中,rsyslogd则监听该文件以获取用户进程的输出。内核日志在老系统上是通过rklogd守护进程管理的,rsyslogd利用以下技术实现了同样的功能:内核日志有printk等函数打印至内核的环状缓存(ring buffer)中,环状缓存的内容直接映射到/proc/kmsg文件中,rsyslogd则通过读取该文件获得内核日志。

rsyslogd守护进程在接收到用户进程或内核输入的日志后,会把它们输出至特定的日志文件,默认,调试信息会保存至/var/log/debug文件,普通信息保存至/var/log/messages文件,内核消息保存至/var/log/kern.log文件,但日志信息具体如何分发,可在rsyslogd的配置文件中设置。rsyslogd的主配置文件是/etc/rsyslog.conf,其中主要可以设置的内容包括:内核日志输入路径(接收来自操作系统内核的日志消息的路径),是否接收UDP日志及其监听端口(默认为514,见/etc/services文件),是否接收TCP日志及其监听端口,日志文件的权限,包含哪些子配置文件(如/etc/rsyslog.d/*.conf)。rsyslogd的子配置文件则指定各类日志的目标存储文件。
在这里插入图片描述
上图中的dmesg是一个Linux和Unix系统中的命令行工具,用于显示系统的内核消息。

应用进程使用syslog函数与rsyslogd守护进程通信:
在这里插入图片描述
syslog函数使用可变参数(第二个参数message和后面的可变参数列表)来结构化输出。priority参数是设施值和日志级别的按位或,设施值的默认值是LOG_USER,日志级别有以下几个:
在这里插入图片描述
openlog函数可改变syslog函数的默认输出方式,进一步结构化日志内容:
在这里插入图片描述
ident参数指定的字符串被添加到日志消息的日期和时间之后,它通常被设置为程序的名字。logopt参数对后续syslog函数的行为进行配置,它可取以下值的按位或:
在这里插入图片描述
facility参数修改syslog函数中的默认设施值。

程序在开发阶段可能需要输出很多调试信息,而发布后又需要将这些调试信息关闭,解决这个问题的方法不是在程序发布后删除调试代码(因为日后我们可能还需要用到),而是简单地设置日志掩码,使日志级别大于日志掩码的日志被系统忽略。setlogmask函数可用于设置syslog的日志掩码:
在这里插入图片描述
maskpri参数指定日志掩码值,该函数始终会成功,它返回调用进程先前的日志掩码值。最后使用closelog函数关闭日志功能:
在这里插入图片描述
大部分服务器需要以root身份启动,但不能以root身份运行,以下函数可获取和设置当前进程的真实用户ID(UID)、有效用户ID(EUID)、真实组(GID)、有效组(EGID):
在这里插入图片描述
一个进程拥有两个用户ID:UID和EUID,EUID存在的目的是方便资源访问,它使得运行程序的用户拥有该程序的有效用户的权限,如su程序,任何用户都可使用它修改自己的账户信息,但修改账户时su程序需要访问/etc/passwd文件,而访问该文件需要root权限,用ls命令可以看到,su程序的所用者是root,且它被设置了set-user-id标志,这个标志表示,任何用户运行su程序时,其有效用户id就是该程序的所有者(即root),因此根据有效用户的含义,任何运行su程序的用户都能访问/etc/passwd文件。有效用户为root的进程称为特权进程(privileged processes)。EGID的含义与EUID类似,能给运行目标程序的用户提供有效组的权限。

可用以下程序测试进程的UID和EUID的区别:

#include <unistd.h>
#include <stdio.h>

int main() {
    uid_t uid = getuid();
    uid_t euid = geteuid();
    printf("userid is %d, effective userid is: %d\n", uid, euid);
    return 0;
}

将以上程序(名为test_uid)的所有者设置为root,并设置该文件的set-user-id标志,然后运行该程序以查看UID和EUID,具体操作如下:
在这里插入图片描述
由上图,进程UID是启动程序的用户的ID,而EUID是root账户(文件所有者)的ID。

以下代码将以root身份启动的进程切换为真实用户ID运行:

static bool switch_to_user(uid_t user_id, gid_t gp_id) {
    // 确保目标用户不是root,root用户的用户ID和组ID都是0
    if ((user_id == 0) && (gp_id == 0)) {
        return false;
    }

    gid_t gid = getgid();
    uid_t uid = getuid();
    // 如果当前用户不是root也不是目标用户
    if (((gid != 0) || (uid != 0)) && ((gid != gp_id) || (uid != user_id))) {
        return false;
    }

    // 如果不是root,则已经是目标用户了
    if (uid != 0) {
        return true;
    }

    // 切换到目标用户
    if ((setgid(gp_id) < 0) || (setuid(user_id) < 0)) {
        return false;
    }

    return true;
}

Linux下每个进程都属于一个进程组,因此进程除了有PID信息外,还有进程组ID(PGID),可用getpgid函数获取指定进程的PGID:
在这里插入图片描述
getpgid函数成功时返回pid参数表示进程所属的进程组PGID,失败则返回-1并设置errno。

每个进程组都有一个首领进程,其PGID和PID相同,进程组将一直存在,直到其中所有进程都离开进程组中(终止或者加入到其他进程组)。

以下函数用于设置PGID:
在这里插入图片描述
setpgid函数将参数pid表示进程的PGID设置为参数pgid。如果pid和pgid参数相同,则由pid参数指定的进程将被设置为进程组首领,如果pid参数为0,则表示把当前进程的PGID设置为pgid参数,如果pgid参数为0,则表示将pid参数指定的进程组ID设置为本进程的PID。setpgid函数成功时返回0,失败时返回-1并设置errno。

一个进程只能设置自己或其子进程的PGID,且子进程调用exec系列函数后,我们就不能在父进程中对它设置PGID。

setsid函数用于创建一个会话:
在这里插入图片描述
setsid进程不能由进程组的首领进程调用,否则将产生一个错误。对于非组首领的进程,调用该函数会创建新会话,且还有如下额外效果:
1.调用进程成为会话的首领,此时该进程是新会话的唯一成员。

2.新建一个进程组,其PGID就是调用进程的PID,调用进程成为该组的首领。

3.调用进程将失去终端(如果有的话)。

setsid函数成功时返回新进程组的PGID,失败则返回-1并设置errno。

Linux进程并未提供所谓会话ID(SID)的概念,但Linux系统认为它等于会话首领所在的进程组的PGID,并提供了函数getsid来读取SID:
在这里插入图片描述
可用ps命令查看进程、进程组、会话之间的关系:
在这里插入图片描述
我们是在bash shell下执行ps和less命令的,所以ps和less命令的父进程是bash命令,这可从PPID(父进程PID)一列看出。这3条命令创建了一个会话(SID是1943)和2个进程组(PGID分别是1942和2298)。bash命令的PID、PGID、SID都相同,说明它是会话的首领,也是组1943的首领。ps命令则是组2298的首领,因为其PID也是2298。下图描述了上图中3个进程的关系:
在这里插入图片描述
Linux上运行的程序会受到资源限制的影响,如物理设备限制(CPU、内存等)、系统策略限制(CPU时间等)、具体实现的限制(文件名的最大长度等)。Linux系统资源限制可通过以下函数来读取或设置:
在这里插入图片描述
在这里插入图片描述
rlim参数是rlimit类型的指针:
在这里插入图片描述
rlim_t是一个整数类型,它描述资源量。rlim_cur成员指定资源的软限制,rlim_max成员指定资源的硬限制。软限制是一个建议性的,最好不要超越的限制,如果超越,系统可能向进程发送信号以终止其运行,例如,当进程CPU时间超过其软限制时,系统将向进程发送SIGXCPU信号,当文件尺寸超过其软限制时,系统将向进程发送SIGXFSZ信号。硬限制一般是软限制的上限,普通程序可以减小硬限制,而只有以root身份运行的程序才能增加硬限制。我们可用ulimit命令修改当前shell环境下的资源限制(软限制和硬限制),这种修改将对该shell启动的所有后续程序有效,我们也可以通过修改配置文件来改变系统软限制和硬限制,且这种修改是永久的。

resource参数指定资源限制类型,下标列出了部分比较重要的资源限制类型:
在这里插入图片描述
setrlimit和getrlimit函数成功时返回0,失败则返回-1并设置errno。

有些服务器程序还需改变工作目录和根目录,一般,Web服务器的逻辑根目录并非文件系统的根目录,而是站点的根目录(对于Linux的Web服务来说,该目录一般是/var/www)。

获取进程当前工作目录和改变进程工作目录的函数:
在这里插入图片描述
buf参数指向的内存用于存储进程当前工作目录的绝对路径名,其大小由size参数指定。如果当前工作目录的绝对路径长度加上结束字符\0超过了size参数,则getcwd函数将返回NULL,并将errno设为ERANGE。如果buf参数为NULL且size参数非0,则getcwd函数可能在内部使用malloc函数动态分配内存,并将进程的当前工作目录存储在其中,此时我们必须自己释放getcwd函数在内部创建的这块内存。getcwd函数成功时返回一个指向目标存储区的指针(指向buf参数或getcwd函数在内部动态创建的缓存区),失败则返回NULL并设置errno。

chdir函数的path参数指定要切换到的目标目录,它成功时返回0,失败时返回-1并设置errno。

chroot函数改变进程根目录:
在这里插入图片描述
path参数指定要切换到的目标根目录,它成功时返回0,失败时返回-1并设置errno。chroot函数不改变进程的当前工作目录,所以调用chroot后,我们仍需使用chdir("/")将工作目录切换到新的根目录。改变进程的根目录后,我们可能无法访问类似/dev的文件或目录,因为它们并非处于新的根目录之下,但调用chroot后,进程原先打开的文件描述符依然生效,所以我们可以利用这些早先打开的文件描述符来访问调用chroot后不能直接访问的文件或目录,尤其是一些日志文件。只有特权进程才能改变根目录。

以下函数可以让一个进程以守护进程的方式运行:

bool daemonize() {
    // 创建子进程,关闭父进程,这样子进程就不是进程组首进程,就可以调用setsid了
    pid_t pid = fork();
    if (pid < 0) {
        return false;
    } else if (pid > 0) {
        exit(0);
    }

    // 设置文件权限掩码,这样当进程创建新文件时,文件的额权限将是0777
    umask(0);

    // 创建新会话,本进程将成为进程组的首领
    pid_t sid = setsid();
    if (sid < 0) {
        return false;
    }

    // 切换工作目录,防止当前工作目录所在文件系统不能卸载
    if ((chdir("/")) < 0) {
        return false;
    }

    // 关闭所有文件描述符
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
    // 此处省略了关闭其他已打开的文件描述符的代码

    // 将标准输入、标准输出、标准错误重定向到/dev/null文件
    open("/dev/null", O_RDONLY);
    open("/dev/null", O_RDWR);
    open("/dev/null", O_RDWR);
    return true;
}

Linux提供了完成以上功能的库函数:
在这里插入图片描述
nochdir参数用于指定是否改变工作目录,如果传0,则工作目录将被设为根目录,否则继续使用当前工作目录。noclose参数传0表示将标准输入、标准输出、标准错误重定向到/dev/null文件,否则不改变这些文件描述符。daemon函数成功时返回0,失败则返回-1并设置errno。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1034982.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI智能文案写作工具,迅速生成高质量的文案

大家好&#xff0c;欢迎来到这篇文章。在信息时代&#xff0c;文字的力量愈发重要&#xff0c;无论是用于广告、文章还是社交媒体&#xff0c;优质的文案都能够吸引更多的注意力。但是&#xff0c;对于许多人来说&#xff0c;创作文案可能是一项繁琐且耗时的任务。 147GPT批量文…

黑马JVM总结(二十三)

&#xff08;1&#xff09;字节码指令-init 方法体内有一些字节&#xff0c;对应着将来要由java虚拟机执行方法内的代码&#xff0c;构造方法里5个字节代码&#xff0c;main方法里有9个字节的代码 java虚拟机呢内部有一个解释器&#xff0c;这个解释器呢可以识别平台无关的字…

【算法】滑动窗口破解长度最小子数组

Problem: 209. 长度最小的子数组 文章目录 题意分析算法原理讲解暴力枚举O(N^2)利用单调性&#xff0c;滑动窗口求解 复杂度Code 题意分析 首先来分析一下本题的题目意思 题目中会给到一个数组&#xff0c;我们的目的是找出在这个数组中 长度最小的【连续】子数组&#xff0c;而…

Android滑动片段

本文所有的代码均存于 https://github.com/MADMAX110/BitsandPizzas 回到BitsandPizzas应用&#xff0c;之前已经创建过创建订单和发出反馈等功能。 修改披萨应用&#xff0c;让它使用标签页导航。在工具条下显示一组标签页&#xff0c;每个选项对应一个不同的标签页。用户单击…

asrpro 天问BLOCK 总结

ASRPRO芯片信息 主频240MHz 640KByte SRAM 2-4M FLASH (https://haohaodada.com/jpeguploadfile/twen/ASRPRO/asr_pro_core.pdf) 下载 &#xff08;注意最好用好点的USB转TTL或是网方的下载器&#xff0c;否则会怀疑人生&#xff09; 下载程序步骤 安装VSCODE 在字符模式下&a…

微服务架构设计:构建高可用性和弹性的应用

文章目录 引言微服务架构的基本概念1. 服务单元2. 通信机制3. 自动化部署4. 增量升级5. 监控和日志 设计原则1. 单一职责原则2. 松散耦合3. 有界上下文4. 弹性设计5. API 设计 优势和挑战优势挑战 实际案例&#xff1a;Netflix结论 &#x1f389;欢迎来到架构设计专栏~探索Java…

2020年06月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python编程&#xff08;1~6级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 下面程序执行完毕后&#xff0c;最终的结果是&#xff1f;&#xff08; &#xff09; a[34,17,7,48,10,5] b[] c[] while …

Aqs独占/共享模式

独占锁和共享锁的概念 独占锁也叫排他锁&#xff0c;是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排他锁后&#xff0c;则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。 共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共…

039_小驰私房菜_Camera perfermance debug

全网最具价值的Android Camera开发学习系列资料~ 作者:8年Android Camera开发,从Camera app一直做到Hal和驱动~ 欢迎订阅,相信能扩展你的知识面,提升个人能力~ 一、抓取trace 1. adb shell "echo vendor.debug.trace.perf=1 >> /system/build.prop" 2. …

栈和队列-Java

目录 一、栈 1.1 概念 1.2 栈的使用 1.3 栈的模拟实现 1.4 栈的应用场景 1.5 概念区分 二、队列 2.1 概念 2.2 队列的使用 2.3 队列的模拟实现 2.4 循环队列 三、双端队列 四、面试题 一、栈 1.1 概念 栈&#xff1a;一种特殊的线性表&#xff0c;只允许在固定的一端进行插…

Docker部署ActiveMQ消息中间件

1、准备工作 docker pull webcenter/activemq:5.14.3 Pwd"/data/software/activemq" mkdir ${Pwd}/data -p2、运行容器 docker run -d --name activemq \-p 61616:61616 \-p 8161:8161 \-v ${Pwd}/data:/opt/activemq/data \-v /etc/localtime:/etc/localtime \--r…

【完全二叉树魔法:顺序结构实现堆的奇象】

本章重点 二叉树的顺序结构堆的概念及结构堆的实现堆的调整算法堆的创建堆排序TOP-K问题 1.二叉树的顺序结构 普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构…

JavaScript系列从入门到精通系列第六篇:JavaScript中其他进制数字以及其他的数据类型强制转换为Boolean数据类型

文章目录 一&#xff1a;各种进制数字 1&#xff1a;表示十进制 2&#xff1a;表示16进制 3&#xff1a;表示8进制 4&#xff1a;表示二进制数字 二&#xff1a;其他进制字符穿转数字 三&#xff1a;其他数据类型强制转换为Boolean 1&#xff1a;Number转Boolean 2&am…

【3dmax】怎么将点删除而面保留

在编辑多边形模式下&#xff0c;选择点模式&#xff0c;选择要删除的点&#xff0c;在下拉面板中找到【移除】

eNSP基础网络学习-v02

一、eNSP 1.什么是eNSP eNSP(Enterprise Network Simulation Platform)是一款由华为提供的免费的、可扩展的、图形化操作的网络仿真工具平台&#xff0c;主要对企业网络路由器、交换机进行软件仿真&#xff0c;完美呈现真实设备实景&#xff0c;支持大型网络模拟&#xff0c;让…

Java基础-环境篇:JDK安装与环境变量配置jdk8/11/17(保姆式详解)

目录 一、Java简介 Java版本 名词解释JDK、JRE JDK版本选择 二、JDK的下载 下载方式1&#xff1a; &#xff08;1&#xff09;在Developers页面中间的技术分类部分&#xff0c;选择Java&#xff0c;单击进入&#xff0c;如图所示&#xff1a; &#xff08;2&#xff09;…

set和map的学习

文章目录 1.set的原型2.set的成员函数1.构造函数2.代码演示 3.map的原型4.map的成员函数1.构造函数2.代码演示 5.OJ练习1.前K个高频单词2.两个数组的交集3.随即链表的复制 1.set的原型 template <class T, //set::key_typeclass Compare less<T>,…

【刷题-牛客】链表内指定区间反转

链表定区间翻转链表 题目链接题目描述核心思想详细图解代码实现复杂度分析 题目链接 链表内指定区间反转_牛客题霸_牛客网 (nowcoder.com) 题目描述 核心思想 遍历链表的过程中在进行原地翻转 [m,n]翻转区间记作子链表,找到子链表的 起始节点 left 和 终止节点 right记录在…

ForkJoin详解

1.分支合并 (大数据量的去使用) package com.kuang.forkjoin;import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.function.LongBinaryOperator; import java.util.stre…

【ROS入门】使用 ROS 话题(Topic)机制实现消息发布与订阅及launch文件的封装

文章结构 任务要求话题模型实现步骤创建工作空间并初始化创建功能包并添加依赖创建发布者代码&#xff08;C&#xff09;创建订阅方代码&#xff08;C&#xff09;配置CMakeLists.txt执行启动roscore编译启动发布和订阅节点 launch封装执行 任务要求 使用 ROS 话题(Topic)机制…