38、Unix 时间处理函数全解析

Unix 时间处理函数全解析

1. 获取当前时间

在 Unix 系统中,有多种方式可以获取当前时间,不同的函数适用于不同的场景和精度要求。

  • time() 函数
    • 功能 :返回自纪元(epoch)以来经过的秒数。如果参数 t 不为 NULL ,函数还会将当前时间写入该指针。
    • 错误处理 :出错时返回 -1(转换为 time_t 类型),并设置 errno EFAULT ,表示 t 是无效指针。
    • 示例代码
time_t t;
printf ("current time: %ld\n", (long) time (&t));
printf ("the same value: %ld\n", (long) t);
  • gettimeofday() 函数
    • 功能 :扩展了 time() 函数,提供微秒级分辨率。
    • 参数 tv 指向 struct timeval 结构体,用于存储当前时间; tz 已过时,在 Linux 上应始终传递 NULL
    • 返回值 :成功时返回 0,失败时返回 -1,并设置 errno EFAULT ,表示 tv tz 是无效指针。
    • 示例代码
#include <sys/time.h>
struct timeval tv;
int ret;
ret = gettimeofday (&tv, NULL);
if (ret)
    perror ("gettimeofday");
else
    printf ("seconds=%ld useconds=%ld\n",
            (long) tv.tv_sec, (long) tv.tv_usec);
  • clock_gettime() 函数
    • 功能 :POSIX 提供的接口,用于获取特定时间源的时间,支持纳秒级精度。
    • 参数 clock_id 表示时间源, ts 指向 struct timespec 结构体,用于存储时间。
    • 错误处理 :失败时返回 -1,并设置 errno EFAULT ts 是无效指针)或 EINVAL clock_id 是无效时间源)。
    • 示例代码
#include <time.h>
clockid_t clocks[] = {
    CLOCK_REALTIME,
    CLOCK_MONOTONIC,
    CLOCK_PROCESS_CPUTIME_ID,
    CLOCK_THREAD_CPUTIME_ID,
    CLOCK_MONOTONIC_RAW,
    (clockid_t) -1 };
int i;
for (i = 0; clocks[i] != (clockid_t) -1; i++) {
    struct timespec ts;
    int ret;
    ret = clock_gettime (clocks[i], &ts);
    if (ret)
        perror ("clock_gettime");
    else
        printf ("clock=%d sec=%ld nsec=%ld\n",
                clocks[i], ts.tv_sec, ts.tv_nsec);
}
2. 获取进程时间

times() 系统调用用于检索运行进程及其子进程的进程时间,以时钟滴答数表示。
- 结构体定义

#include <sys/times.h>
struct tms {
    clock_t tms_utime;   /* 用户时间消耗 */
    clock_t tms_stime;   /* 系统时间消耗 */
    clock_t tms_cutime;  /* 子进程消耗的用户时间 */
    clock_t tms_cstime;  /* 子进程消耗的系统时间 */
};
  • 函数调用
clock_t times (struct tms *buf);
  • 功能 :成功时,将调用进程及其子进程消耗的进程时间填充到 buf 指向的 tms 结构体中。调用返回自过去某个任意但固定点以来经过的时钟滴答数。
  • 错误处理 :失败时返回 -1,并设置 errno EFAULT ,表示 buf 是无效指针。
3. 设置当前时间

有时应用程序需要将当前时间和日期设置为指定值,以下是相关函数。
- stime() 函数
- 功能 :将系统时间设置为 t 指向的值。
- 权限要求 :调用用户必须具有 CAP_SYS_TIME 能力,通常只有 root 用户拥有此能力。
- 错误处理 :失败时返回 -1,并设置 errno EFAULT t 是无效指针)或 EPERM (调用用户不具备 CAP_SYS_TIME 能力)。
- 示例代码

#define _SVID_SOURCE
#include <time.h>
time_t t = 1;
int ret;
/* set time to one second after the epoch */
ret = stime (&t);
if (ret)
    perror ("stime");
  • settimeofday() 函数
    • 功能 :将系统时间设置为 tv 指定的值。
    • 参数 tv 指向 struct timeval 结构体, tz 应传递 NULL
    • 错误处理 :失败时返回 -1,并设置 errno EFAULT tv tz 指向无效内存区域)、 EINVAL (提供的结构体字段无效)或 EPERM (调用进程缺乏 CAP_SYS_TIME 能力)。
    • 示例代码
#include <sys/time.h>
struct timeval tv = { .tv_sec  = 31415926,
                      .tv_usec = 27182818 };
int ret;
ret = settimeofday (&tv, NULL);
if (ret)
    perror ("settimeofday");
  • clock_settime() 函数
    • 功能 :将指定时间源的时间设置为 ts 指定的值。
    • 参数 clock_id 表示时间源, ts 指向 struct timespec 结构体。
    • 错误处理 :失败时返回 -1,并设置 errno EFAULT ts 是无效指针)、 EINVAL clock_id 是无效时间源)或 EPERM (进程缺乏设置指定时间源的权限)。
    • 优势 :在大多数系统上,唯一可设置的时间源是 CLOCK_REALTIME ,该函数的优势在于提供纳秒级精度。
4. 时间转换函数

Unix 系统和 C 语言提供了一组函数,用于在分解时间(时间的 ASCII 字符串表示)和 time_t 之间进行转换。
| 函数名 | 功能 | 线程安全性 |
| ---- | ---- | ---- |
| asctime() | 将 struct tm 结构体转换为 ASCII 字符串 | 非线程安全 |
| asctime_r() | 线程安全版本的 asctime() | 线程安全 |
| mktime() | 将 struct tm 结构体转换为 time_t | |
| ctime() | 将 time_t 转换为 ASCII 字符串 | 非线程安全 |
| ctime_r() | 线程安全版本的 ctime() | 线程安全 |
| gmtime() | 将 time_t 转换为 UTC 时区的 struct tm 结构体 | 非线程安全 |
| gmtime_r() | 线程安全版本的 gmtime() | 线程安全 |
| localtime() | 将 time_t 转换为用户时区的 struct tm 结构体 | 非线程安全 |
| localtime_r() | 线程安全版本的 localtime() | 线程安全 |
| difftime() | 返回两个 time_t 值之间经过的秒数 | |

5. 系统时钟调整

大而突然的时钟时间跳跃可能会对依赖绝对时间运行的应用程序造成严重破坏,以下是相关调整函数。
- adjtime() 函数
- 功能 :逐渐调整当前时间,以避免对依赖绝对时间的应用程序造成影响。
- 参数 delta 指向 struct timeval 结构体,指定时间调整量; olddelta 用于存储之前未应用的调整量。
- 调整方式 :如果 delta 为正,内核加速系统时钟;如果为负,内核减慢系统时钟。
- 错误处理 :失败时返回 -1,并设置 errno EFAULT delta olddelta 是无效指针)、 EINVAL (调整量过大或过小)或 EPERM (调用用户不具备 CAP_SYS_TIME 能力)。
- adjtimex() 函数
- 功能 :Linux 实现的更复杂的时钟调整算法。
- 参数 adj 指向 struct timex 结构体,用于读取和设置内核时间相关参数。
- 返回值 :成功时返回当前时钟状态,失败时返回 -1,并设置 errno EFAULT adj 是无效指针)、 EINVAL modes offset tick 无效)或 EPERM modes 非零,但调用用户不具备 CAP_SYS_TIME 能力)。

6. 进程睡眠函数

进程可以使用以下函数暂停执行一段时间。
- sleep() 函数
- 功能 :使调用进程睡眠指定的秒数。
- 返回值 :返回未睡眠的秒数,成功时返回 0。
- 示例代码

#include <unistd.h>
sleep (7);        /* sleep seven seconds */
  • usleep() 函数
    • 功能 :使调用进程睡眠指定的微秒数。
    • 原型差异 :BSD 版本和 SUSv2 版本的原型不同,为了保持可移植性,应假设参数为 unsigned int ,并忽略返回值。
    • 示例代码
unsigned int usecs = 200;
usleep (usecs);

综上所述,Unix 系统提供了丰富的时间处理函数,涵盖了时间获取、设置、转换、调整以及进程睡眠等多个方面。开发者可以根据具体需求选择合适的函数来处理时间相关的任务。在使用这些函数时,需要注意权限要求、错误处理以及线程安全性等问题,以确保程序的正确性和稳定性。

graph TD;
    A[获取当前时间] --> B[time()];
    A --> C[gettimeofday()];
    A --> D[clock_gettime()];
    E[设置当前时间] --> F[stime()];
    E --> G[settimeofday()];
    E --> H[clock_settime()];
    I[时间转换] --> J[asctime()];
    I --> K[mktime()];
    I --> L[ctime()];
    I --> M[gmtime()];
    I --> N[localtime()];
    I --> O[difftime()];
    P[系统时钟调整] --> Q[adjtime()];
    P --> R[adjtimex()];
    S[进程睡眠] --> T[sleep()];
    S --> U[usleep()];

Unix 时间处理函数全解析

7. 不同时间函数的使用场景分析

不同的时间函数适用于不同的场景,下面为你详细分析:
| 函数类型 | 适用场景 | 示例场景 |
| ---- | ---- | ---- |
| 获取当前时间 | 需要获取高精度时间或特定时间源的时间 | 用于性能测试,精确记录操作开始和结束时间 |
| 设置当前时间 | 系统时间校准或模拟特定时间 | 测试程序在不同时间下的运行情况 |
| 时间转换 | 在不同时间表示形式之间转换 | 显示用户可读的时间信息 |
| 系统时钟调整 | 避免系统时间突变对应用程序造成影响 | 运行依赖时间戳的构建工具(如 make)时 |
| 进程睡眠 | 程序需要暂停执行一段时间 | 定时任务,周期性执行某些操作 |

8. 时间处理函数的错误处理最佳实践

在使用时间处理函数时,正确的错误处理至关重要,以下是一些最佳实践:
- 检查返回值 :大多数时间处理函数在失败时会返回特定的值(如 -1),应检查返回值以判断函数是否执行成功。
- 设置 errno :失败时,函数通常会设置 errno 变量,可根据 errno 的值进行相应的错误处理。
- 使用 perror() :可以使用 perror() 函数输出错误信息,方便调试。

以下是一个示例代码,展示了如何进行错误处理:

#include <stdio.h>
#include <time.h>
#include <errno.h>

int main() {
    time_t t;
    int ret;

    ret = stime(&t);
    if (ret == -1) {
        switch (errno) {
            case EFAULT:
                perror("stime: Invalid pointer");
                break;
            case EPERM:
                perror("stime: Permission denied");
                break;
            default:
                perror("stime: Unknown error");
                break;
        }
    }
    return 0;
}
9. 多线程环境下的时间处理

在多线程环境中,需要特别注意时间处理函数的线程安全性。一些函数(如 asctime() ctime() gmtime() localtime() )是非线程安全的,因为它们返回指向静态分配内存的指针,后续调用可能会覆盖该内存。

为了确保线程安全,应使用这些函数的线程安全版本(如 asctime_r() ctime_r() gmtime_r() localtime_r() )。以下是一个多线程使用 asctime_r() 的示例:

#include <stdio.h>
#include <time.h>
#include <pthread.h>

void *thread_function(void *arg) {
    time_t t = time(NULL);
    struct tm *tm_info = localtime(&t);
    char buf[26];

    asctime_r(tm_info, buf);
    printf("Thread time: %s", buf);

    return NULL;
}

int main() {
    pthread_t thread;
    int ret;

    ret = pthread_create(&thread, NULL, thread_function, NULL);
    if (ret != 0) {
        perror("pthread_create");
        return 1;
    }

    ret = pthread_join(thread, NULL);
    if (ret != 0) {
        perror("pthread_join");
        return 1;
    }

    return 0;
}
10. 总结与建议
  • 总结 :Unix 系统提供了丰富的时间处理函数,涵盖了时间获取、设置、转换、调整以及进程睡眠等多个方面。每个函数都有其特定的功能和适用场景,开发者需要根据具体需求进行选择。
  • 建议
    • 在使用时间处理函数时,要注意权限要求,特别是设置时间的函数,通常需要特定的权限。
    • 对于非线程安全的函数,在多线程环境中应使用其线程安全版本。
    • 正确处理函数调用的错误,通过检查返回值和 errno 变量,确保程序的健壮性。
graph LR;
    A[时间处理需求] --> B{选择合适的函数类型};
    B --> C[获取当前时间];
    B --> D[设置当前时间];
    B --> E[时间转换];
    B --> F[系统时钟调整];
    B --> G[进程睡眠];
    C --> H{高精度或特定时间源?};
    H --> |是| I[clock_gettime()];
    H --> |否| J[time()];
    D --> K{需要高精度?};
    K --> |是| L[clock_settime()];
    K --> |否| M[stime()];
    E --> N{转换方向};
    N --> |tm 到 ASCII| O[asctime()];
    N --> |tm 到 time_t| P[mktime()];
    N --> |time_t 到 ASCII| Q[ctime()];
    N --> |time_t 到 tm| R[gmtime()/localtime()];
    F --> S{简单调整或复杂调整};
    S --> |简单| T[adjtime()];
    S --> |复杂| U[adjtimex()];
    G --> V{秒级或微秒级};
    V --> |秒级| W[sleep()];
    V --> |微秒级| X[usleep()];

通过以上的分析和示例,希望你能更好地理解和使用 Unix 系统中的时间处理函数,在实际开发中能够根据具体需求选择合适的函数,并正确处理可能出现的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值