强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

POSIX 标准详解教程 / 第十一章:环境与配置

第十一章:环境与配置

掌握 POSIX 环境变量、locale 国际化、sysconf 系统配置、路径配置。


11.1 环境变量

11.1.1 标准环境变量

变量说明典型值
HOME用户主目录/home/user
PATH可执行文件搜索路径/usr/bin:/bin
USER / LOGNAME当前用户名root
SHELL用户默认 Shell/bin/bash
LANG默认 localeen_US.UTF-8
TERM终端类型xterm-256color
PWD当前工作目录/home/user/project
LD_LIBRARY_PATH动态链接库搜索路径/usr/local/lib
TMPDIR临时文件目录/tmp
TZ时区Asia/Shanghai

11.1.2 环境变量操作

/*
 * env_advanced.c - 环境变量高级操作
 * 编译: gcc -Wall -o env_advanced env_advanced.c
 */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern char **environ;

/* 安全获取环境变量,带默认值 */
static const char *env_get(const char *key, const char *def)
{
    const char *val = getenv(key);
    return val ? val : def;
}

/* 打印所有环境变量(带过滤) */
static void print_env(const char *prefix)
{
    printf("以 '%s' 开头的环境变量:\n", prefix ? prefix : "");
    for (char **ep = environ; *ep; ep++) {
        if (!prefix || strncmp(*ep, prefix, strlen(prefix)) == 0)
            printf("  %s\n", *ep);
    }
}

int main(void)
{
    /* 1. 带默认值的安全获取 */
    printf("HOME: %s\n", env_get("HOME", "/tmp"));
    printf("SHELL: %s\n", env_get("SHELL", "/bin/sh"));
    printf("MY_MISSING: %s\n", env_get("MY_MISSING", "(未设置)"));

    /* 2. putenv vs setenv 区别 */
    /* putenv: 直接设置 "KEY=VALUE" 字符串(不复制 key) */
    /* setenv: 分别提供 key 和 value,可控制覆盖 */
    setenv("DEMO_KEY", "demo_value", 1);
    printf("DEMO_KEY: %s\n", getenv("DEMO_KEY"));

    /* 3. 遍历环境变量(前 10 个) */
    printf("\n前 10 个环境变量:\n");
    int count = 0;
    for (char **ep = environ; *ep && count < 10; ep++, count++)
        printf("  [%d] %s\n", count, *ep);

    /* 4. clearenv() 清除所有环境变量(危险,谨慎使用) */
    /* clearenv(); */

    return 0;
}

11.2 POSIX Locale(区域设置)

11.2.1 Locale 类别

类别影响范围
LC_CTYPE字符分类字母/数字判断、大小写转换
LC_COLLATE排序规则字符串比较和排序
LC_TIME日期/时间格式strftime() 输出格式
LC_NUMERIC数字格式小数点符号(1,000.00 vs 1.000,00)
LC_MONETARY货币格式货币符号、分隔符
LC_MESSAGES消息语言系统提示信息语言
LC_ALL设置所有类别覆盖所有单独设置

11.2.2 Locale 操作

/*
 * locale_demo.c - POSIX locale 操作演示
 * 编译: gcc -Wall -o locale_demo locale_demo.c
 */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <ctype.h>

int main(void)
{
    /* 1. 设置 locale 为环境变量指定的值 */
    char *old = setlocale(LC_ALL, "");
    printf("默认 locale: %s\n", old ? old : "(C)");

    /* 2. 查询当前 locale */
    printf("当前 LC_CTYPE: %s\n", setlocale(LC_CTYPE, NULL));
    printf("当前 LC_TIME: %s\n", setlocale(LC_TIME, NULL));

    /* 3. 字符分类与 locale */
    printf("\n字符分类 (LC_CTYPE):\n");
    const char *test = "Hello 你好 123!@#";
    for (const char *p = test; *p; p++) {
        printf("  '%c' isalpha=%d isdigit=%d isprint=%d\n",
               *p, isalpha((unsigned char)*p),
               isdigit((unsigned char)*p),
               isprint((unsigned char)*p));
    }

    /* 4. 时间格式与 locale */
    printf("\n不同 locale 的日期格式:\n");
    time_t now = time(NULL);
    struct tm tm;
    localtime_r(&now, &tm);
    char buf[128];

    /* C locale */
    setlocale(LC_TIME, "C");
    strftime(buf, sizeof(buf), "%A, %B %d, %Y", &tm);
    printf("  C:        %s\n", buf);

    /* 中文 locale (如果可用) */
    if (setlocale(LC_TIME, "zh_CN.UTF-8")) {
        strftime(buf, sizeof(buf), "%Y年%m月%d日 %A", &tm);
        printf("  zh_CN:    %s\n", buf);
    }

    /* 英文 locale */
    if (setlocale(LC_TIME, "en_US.UTF-8")) {
        strftime(buf, sizeof(buf), "%A, %B %d, %Y", &tm);
        printf("  en_US:    %s\n", buf);
    }

    /* 5. 数字格式 */
    struct lconv *lc;
    setlocale(LC_ALL, "C");
    lc = localeconv();
    printf("\nC locale 数字格式:\n");
    printf("  小数点: '%s'\n", lc->decimal_point);
    printf("  千位分隔: '%s'\n", lc->thousands_sep);

    return 0;
}

11.3 sysconf():系统配置查询

11.3.1 常用配置项

/*
 * sysconf_demo.c - 查询系统配置限制
 * 编译: gcc -Wall -o sysconf_demo sysconf_demo.c
 */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <limits.h>

static void query_conf(const char *name, int sc_name)
{
    long val = sysconf(sc_name);
    if (val == -1) {
        printf("  %-35s: 不支持或无限制\n", name);
    } else {
        printf("  %-35s: %ld\n", name, val);
    }
}

int main(void)
{
    printf("=== 进程相关 ===\n");
    query_conf("最大子进程数", _SC_CHILD_MAX);
    query_conf("每用户最大进程数", _SC_NPROCESSORS_CONF);
    query_conf("进程组最大数量", _SC_CHILD_MAX);

    printf("\n=== 文件相关 ===\n");
    query_conf("最大打开文件数", _SC_OPEN_MAX);
    query_conf("文件名最大长度", _SC_NAME_MAX);
    query_conf("路径名最大长度", _SC_PATH_MAX);
    query_conf("管道缓冲区大小", _SC_PIPE_BUF);

    printf("\n=== 内存相关 ===\n");
    query_conf("物理页面大小", _SC_PAGESIZE);
    query_conf("物理页面数", _SC_PHYS_PAGES);
    query_conf("可用页面数", _SC_AVPHYS_PAGES);

    printf("\n=== 系统相关 ===\n");
    query_conf("处理器数量(在线)", _SC_NPROCESSORS_ONLN);
    query_conf("处理器数量(配置)", _SC_NPROCESSORS_CONF);
    query_conf("时钟滴答频率(Hz)", _SC_CLK_TCK);
    query_conf("主机名最大长度", _SC_HOST_NAME_MAX);
    query_conf("登录名最大长度", _SC_LOGIN_NAME_MAX);

    printf("\n=== POSIX 版本 ===\n");
    query_conf("POSIX 版本", _SC_VERSION);
    query_conf("POSIX.2 版本", _SC_2_VERSION);

    printf("\n=== 路径配置 ===\n");
    char *path;
    path = confstr(_CS_PATH, NULL, 0);
    if (path > 0) {
        char *buf = (char *)path;
        buf = __builtin_alloca((size_t)path);
        confstr(_CS_PATH, buf, (size_t)path);
        printf("  默认 PATH: %s\n", buf);
    }

    return 0;
}

11.4 路径配置:confstr()

/*
 * confstr_demo.c - 查询系统路径配置
 * 编译: gcc -Wall -o confstr_demo confstr_demo.c
 */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <string.h>

static void query_path(const char *name, int cs_name)
{
    size_t len = confstr(cs_name, NULL, 0);
    if (len == 0) {
        printf("  %-20s: 不支持\n", name);
        return;
    }
    char *buf = malloc(len);
    if (!buf) return;
    confstr(cs_name, buf, len);
    printf("  %-20s: %s\n", name, buf);
    free(buf);
}

int main(void)
{
    printf("=== 系统路径配置 (confstr) ===\n");
    query_path("PATH", _CS_PATH);

    /* 平台相关路径 */
#ifdef _CS_GNU_LIBC_VERSION
    query_path("glibc 版本", _CS_GNU_LIBC_VERSION);
#endif
#ifdef _CS_GNU_LIBPTHREAD_VERSION
    query_path("pthread 版本", _CS_GNU_LIBPTHREAD_VERSION);
#endif

    /* 编译路径 */
#ifdef _CS_POSIX_V7_ILP32_OFF32_CFLAGS
    query_path("ILP32 CFLAGS", _CS_POSIX_V7_ILP32_OFF32_CFLAGS);
#endif

    return 0;
}

11.5 用户与组信息

/*
 * user_info.c - 获取用户和组信息
 * 编译: gcc -Wall -o user_info user_info.c
 */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>

int main(void)
{
    /* 当前用户信息 */
    uid_t uid = getuid();
    uid_t euid = geteuid();
    gid_t gid = getgid();
    gid_t egid = getegid();

    printf("=== 当前进程身份 ===\n");
    printf("  实际 UID: %d\n", uid);
    printf("  有效 UID: %d\n", euid);
    printf("  实际 GID: %d\n", gid);
    printf("  有效 GID: %d\n", egid);

    /* 用户信息查询 */
    struct passwd *pw = getpwuid(uid);
    if (pw) {
        printf("\n=== 用户信息 ===\n");
        printf("  用户名: %s\n", pw->pw_name);
        printf("  全名: %s\n", pw->pw_gecos);
        printf("  主目录: %s\n", pw->pw_dir);
        printf("  Shell: %s\n", pw->pw_shell);
        printf("  UID: %d, GID: %d\n", pw->pw_uid, pw->pw_gid);
    }

    /* 组信息查询 */
    struct group *gr = getgrgid(gid);
    if (gr) {
        printf("\n=== 主组信息 ===\n");
        printf("  组名: %s\n", gr->gr_name);
        printf("  GID: %d\n", gr->gr_gid);
        printf("  组成员: ");
        for (char **members = gr->gr_mem; *members; members++)
            printf("%s ", *members);
        printf("\n");
    }

    /* 获取所有所属组 */
    int ngroups = 0;
    getgroups(0, NULL);  /* 查询数量 */
    gid_t *groups = malloc(sizeof(gid_t) * 64);
    ngroups = getgroups(64, groups);
    printf("\n=== 所属组 (%d 个) ===\n", ngroups);
    for (int i = 0; i < ngroups; i++) {
        gr = getgrgid(groups[i]);
        printf("  %d (%s)\n", groups[i], gr ? gr->gr_name : "?");
    }
    free(groups);

    /* 登录名 */
    printf("\n登录名: %s\n", getlogin());

    return 0;
}

11.6 资源限制:getrlimit/setrlimit

/*
 * resource_limits.c - POSIX 资源限制
 * 编译: gcc -Wall -o resource_limits resource_limits.c
 */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <sys/resource.h>
#include <string.h>

static void show_limit(const char *name, int resource)
{
    struct rlimit rl;
    if (getrlimit(resource, &rl) == -1) {
        printf("  %-30s: 获取失败\n", name);
        return;
    }
    const char *soft = (rl.rlim_cur == RLIM_INFINITY) ? "unlimited" : "";
    const char *hard = (rl.rlim_max == RLIM_INFINITY) ? "unlimited" : "";
    printf("  %-30s: soft=", name);
    if (rl.rlim_cur == RLIM_INFINITY)
        printf("unlimited, hard=");
    else
        printf("%lu, hard=", (unsigned long)rl.rlim_cur);
    if (rl.rlim_max == RLIM_INFINITY)
        printf("unlimited\n");
    else
        printf("%lu\n", (unsigned long)rl.rlim_max);
    (void)soft;
    (void)hard;
}

int main(void)
{
    printf("=== 资源限制 ===\n");
    show_limit("核心文件大小", RLIMIT_CORE);
    show_limit("CPU 时间(秒)", RLIMIT_CPU);
    show_limit("数据段大小", RLIMIT_DATA);
    show_limit("文件大小", RLIMIT_FSIZE);
    show_limit("进程优先级", RLIMIT_NICE);
    show_limit("打开文件数", RLIMIT_NOFILE);
    show_limit("栈大小", RLIMIT_STACK);
    show_limit("地址空间", RLIMIT_AS);

    /* 增加打开文件数限制 */
    struct rlimit rl;
    getrlimit(RLIMIT_NOFILE, &rl);
    printf("\n当前打开文件限制: %lu (软) / %lu (硬)\n",
           (unsigned long)rl.rlim_cur, (unsigned long)rl.rlim_max);

    return 0;
}

11.7 系统信息:uname()

/*
 * uname_info.c - 获取系统信息
 * 编译: gcc -Wall -o uname_info uname_info.c
 */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <sys/utsname.h>

int main(void)
{
    struct utsname info;
    if (uname(&info) == -1) {
        perror("uname");
        return 1;
    }

    printf("=== 系统信息 (uname) ===\n");
    printf("  系统名称: %s\n", info.sysname);
    printf("  主机名:   %s\n", info.nodename);
    printf("  内核版本: %s\n", info.release);
    printf("  内核完整: %s\n", info.version);
    printf("  硬件架构: %s\n", info.machine);

    return 0;
}
$ ./uname_info
=== 系统信息 (uname) ===
  系统名称: Linux
  主机名:   workstation
  内核版本: 6.1.0-generic
  内核完整: #1 SMP PREEMPT_DYNAMIC
  硬件架构: x86_64

11.8 业务场景:程序启动配置探测

/*
 * system_probe.c - 程序启动时探测系统能力
 * 编译: gcc -Wall -o system_probe system_probe.c
 */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/utsname.h>

typedef struct {
    long pagesize;
    long ncpu;
    long max_open_files;
    long max_path;
    long max_name;
    long pipe_buf;
    long phys_mem_pages;
} system_info_t;

static int probe_system(system_info_t *info)
{
    info->pagesize = sysconf(_SC_PAGESIZE);
    info->ncpu = sysconf(_SC_NPROCESSORS_ONLN);
    info->max_open_files = sysconf(_SC_OPEN_MAX);
    info->max_path = pathconf("/", _PC_PATH_MAX);
    info->max_name = pathconf("/", _PC_NAME_MAX);
    info->pipe_buf = pathconf("/", _PC_PIPE_BUF);
    info->phys_mem_pages = sysconf(_SC_PHYS_PAGES);

    if (info->pagesize < 0 || info->ncpu < 0)
        return -1;
    return 0;
}

int main(void)
{
    system_info_t info;
    struct utsname uts;

    if (probe_system(&info) == -1) {
        perror("probe_system");
        return 1;
    }
    uname(&uts);

    printf("=== 系统探测报告 ===\n");
    printf("系统: %s %s (%s)\n", uts.sysname, uts.release, uts.machine);
    printf("CPU 核心数: %ld\n", info.ncpu);
    printf("页面大小: %ld 字节\n", info.pagesize);
    printf("物理内存: %.1f GB\n",
           (double)info.phys_mem_pages * info.pagesize / (1024*1024*1024));
    printf("最大打开文件: %ld\n", info.max_open_files);
    printf("路径最大长度: %ld\n", info.max_path);
    printf("文件名最大长度: %ld\n", info.max_name);
    printf("管道缓冲区: %ld 字节\n", info.pipe_buf);

    /* 根据系统能力调整工作参数 */
    int worker_threads = (info.ncpu > 4) ? info.ncpu - 1 : info.ncpu;
    int io_threads = (info.max_open_files > 1024) ? 4 : 2;
    printf("\n建议配置:\n");
    printf("  工作线程: %d\n", worker_threads);
    printf("  I/O 线程: %d\n", io_threads);

    return 0;
}

11.9 注意事项

⚠️ setenv() 线程安全setenv() 修改全局环境变量,在多线程环境中不安全。如果需要线程安全的环境变量管理,自行加锁。

⚠️ locale 与性能:设置 locale 会影响 ctype 函数(如 toupper())的性能。在性能敏感的代码中,临时使用 setlocale(LC_ALL, "C")

⚠️ sysconf 返回值:返回 -1 且 errno == 0 表示该限制无限制(unlimited)。返回 -1 且 errno != 0 表示错误。

⚠️ 环境变量注入:不应信任环境变量(特别是 LD_LIBRARY_PATHPATH)。SUID 程序中必须清理环境。

⚠️ /proc 文件系统:Linux 提供了 /proc/self/ 目录获取进程信息。这是 Linux 特有的,非 POSIX 标准。


11.10 扩展阅读

  1. man 3 getenvman 3 setlocaleman 3 sysconfman 3 confstr
  2. man 7 environ — 环境变量概述
  3. man 7 locale — locale 概述
  4. man 2 getrlimit — 资源限制
  5. APUE 第 2, 6 章:Unix Standard, System Information

11.11 本章小结

要点说明
环境变量getenv/setenv/unsetenv,注意线程安全
locale国际化设置,影响字符分类、排序、时间格式
sysconf()查询系统运行时限制(页面大小、CPU数等)
confstr()查询系统路径配置
getrlimit/setrlimit进程资源限制(文件数、栈大小等)
uname()获取系统信息(内核版本、架构等)