一些我竟然不知道的系统相关知识 - 这都不知道「持续更新」

最近发现我竟然不知道或理解错误的系统知识,包含Linux系统时区设置、coredump文件命名的问题。

设置Linux时区主要有两种方法,来源与man 5 localtime

  1. 设置/etc/localtime文件链接到/usr/share/zoneinfo目录下对应时区文件

    shell

    $ ll /etc/localtime
    lrwxrwxrwx 1 root root /etc/localtime -> /usr/share/zoneinfo/Asia/Shanghai

    此为系统级别的修改,其文件格式可通过man 5 tzfile查看。timedatectl命令就是对该文件进行修改从而达成修改时区的目的。

  2. 设置环境变量TZ

    设置环境变量TZ可对上述文件设置的时区进行覆盖,一般应用于临时设置,而不作为系统级别的设置。

    shell

    export TZ='Asia/Shanghai'

一般情况下我们的软件都会打开coredump功能,在程序崩溃时将终止时的内存映像保存为文件即coredump,可以使用gdb等工具分析崩溃时程序执行的语句和运行数据。该coredump文件生成的文件名一般配置成包含可执行文件名,这样就可以从coredump文件名就可以知道是哪个程序产生的,从而方便用gdb加载可执行文件。

但在实际工作中,曾多次遇到coredump文件名中可执行文件的名变成其它名称,例如thread_pool等,而不是原本的可执行文件名。这种coredump实际使用gdb打开其实可以看到实际产生的进程名core was generated by /path/your_bin。这个疑点一直埋藏心中。

这次频繁遇到该问题,仔细分析发现是由于日志库的线程设置了线程名称,导致如果该线程收到异常信号生成coredump时,文件命名使用该线程的名称而不是默认的可执行文件名。

设置和获取线程名称的系统调用prctl()如下:

c

#include <linux/prctl.h>  /* Definition of PR_* constants */
#include <sys/prctl.h>

int prctl(PR_SET_NAME, char name[16]);
int prctl(PR_GET_NAME, const char name[16]);

使用ChatGPT写个测试程序来验证下。首先设置coredump的路径和pattern。

shell

sudo sysctl -w kernel.core_pattern="/tmp/core_%e_%p_%t"

下表1为/proc/sys/kernel/core_pattern的说明符,在实际生成coredump时就会替代为对应的值。其中%e就是会被替换成崩溃线程的线程名称。

说明符替代为
%c对核心文件大小的资源软限制(字节数;始于 Linux 2.6.24)
%e可执行文件名(不含路径前缀),准确说是线程名称,线程名称默认是可执行文件名
%g遭转储进程的实际组 ID
%h主机系统的名称
%p遭转储进程的进程 ID
%s导致进程终止的信号编号
%t转储时间,始于 Epoch,以秒为单位
%u遭转储进程的实际用户 ID
%%单个 % 字符

编写如下程序,在创建的线程中打印当前线程名然后设置线程名为TestThread,之后调用raise()触发崩溃产生coredump。

c++

#include <iostream>
#include <pthread.h>
#include <sys/prctl.h>
#include <csignal>
#include <unistd.h>

// 线程函数
void* threadFunc(void* arg) {
    char threadName[16];
    prctl(PR_GET_NAME, threadName);
    std::cout << "Thread name is: " << threadName << std::endl;
    // 设置线程名称
    prctl(PR_SET_NAME, "TestThread");

    // 打印线程名称
    prctl(PR_GET_NAME, threadName);
    std::cout << "Thread name set to: " << threadName << std::endl;

    // 故意引发崩溃
    raise(SIGSEGV); // 发送 SIGSEGV 信号引发段错误

    return nullptr;
}

int main() {
    pthread_t thread;

    // 创建线程
    if (pthread_create(&thread, nullptr, threadFunc, nullptr) != 0) {
        std::cerr << "Failed to create thread" << std::endl;
        return 1;
    }
    // 等待线程完成
    pthread_join(thread, nullptr);

    // raise(SIGSEGV); // 发送 SIGSEGV 信号引发段错误
    return 0;
}

最后编译并运行。

shell

$ g++ test_pr_set_name.cc -o test_pr_set_name -lpthread
$ ulimit -c unlimited && ./test_pr_set_name
Thread name is: test_pr_set_nam
Thread name set to: TestThread
[1]    13480 segmentation fault (core dumped)  ./test_pr_set_name

最后产生的coredump名称为core_TestThread_10757_1724462863,可以看到%e被替换成了设置的线程名称TestThread。而如果将raise放在主线程中执行,则会产生core_test_pr_set_nam_18496_1724463124名称的coredump,原因也很好理解,主线程没有设置线程名称,所以是默认的test_pr_set_nam可执行文件名。而且也说明了线程名最长为15byte+1byte(\0)。