• 首页
  • 搜索
  • 工具
  • 分类
  • 日志
  • 友链
  • 图片

It's Geek KingYoungy

KEEP CHALLENGE

分类 系统编程基础 下的文章

系统编程基础

Linux C编程下的时间

2025-03-23 浏览量 142 暂无评论

Linux C编程下的时间处理

1. time.h(C标准库时间处理)

1.1 时间显示相关函数
// 获取Epoch时间
time_t time(time_t *timer);
  • 功能:获取当前系统时间(自1970-01-01 00:00:00 UTC的秒数)
  • 参数:timer为输出参数(若为NULL则不保存)
  • 返回值:成功返回时间值,失败返回(time_t)-1
// 时间结构体转换
struct tm *localtime(const time_t *timer);
  • 功能:将time_t转换为本地时间的tm结构体
  • 结构体定义:
struct tm {
    int tm_sec;   // 秒 [0-60](含闰秒)
    int tm_min;   // 分 [0-59]
    int tm_hour;  // 时 [0-23]
    int tm_mday;  // 日 [1-31]
    int tm_mon;   // 月 [0-11](0为1月)
    int tm_year;  // 年(实际年份减1900)
    int tm_wday;  // 星期 [0-6](0为周日)
    int tm_yday;  // 年中的第几天 [0-365]
    int tm_isdst; // 夏令时标志(负数表示未知)
};
// 时间格式化
size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr);
  • 功能:将tm结构体按格式转换为字符串
  • 常用格式符:%Y(年)、%m(月)、%d(日)、%H(时)、%M(分)、%S(秒)
  • 返回值:成功返回写入字符数,失败返回0
1.2 POSIX时钟与定时器
// 高精度时间获取
int clock_gettime(clockid_t clk_id, struct timespec *tp);
  • 功能:获取指定时钟的纳秒级时间
  • 时钟类型:

    • CLOCK_REALTIME:系统实时时间(可修改)
    • CLOCK_MONOTONIC:系统启动后的单调时间(不可修改)
    • CLOCK_BOOTTIME:包含系统挂起时间的单调时钟
  • 结构体定义:
struct timespec {
    time_t tv_sec;   // 秒
    long tv_nsec;    // 纳秒 [0-999,999,999]
};
// POSIX间隔式定时器函数
int timer_create(clockid_t clockid, struct sigevent *sevp, timer_t *timerid);
int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
int timer_delete(timer_t timerid);
  • 核心功能:

    • timer_create: 创建定时器,支持多种通知方式(信号/线程)
    • timer_settime: 设置定时器初始和间隔时间(精度达纳秒级)
    • timer_delete: 销毁定时器资源
  • 参数示例:
struct itimerspec {
    struct timespec it_interval; // 间隔时间
    struct timespec it_value;    // 初始到期时间
};

struct sigevent {
    int sigev_notify;            // 通知方式:SIGEV_SIGNAL/SIGEV_THREAD
    int sigev_signo;             // 信号类型(如SIGUSR1)
    union sigval sigev_value;    // 传递的数据
    void (*sigev_notify_function)(union sigval); // 线程处理函数
};

2. sys/time.h(系统级时间与定时器)

2.1 高精度时间获取
int gettimeofday(struct timeval *tv, struct timezone *tz);
  • 功能:获取微秒级时间(兼容旧系统,推荐优先使用clock_gettime)
  • 结构体定义:
struct timeval {
    time_t tv_sec;       // 秒
    suseconds_t tv_usec; // 微秒 [0-999,999]
};
2.2 传统间隔定时器
int setitimer(int which, const struct itimerval *new_val, struct itimerval *old_val);
  • 定时器类型:

    • ITIMER_REAL:真实时间,到期发送SIGALRM
    • ITIMER_VIRTUAL:进程用户态CPU时间,发送SIGVTALRM
    • ITIMER_PROF:总CPU时间(用户态+内核态),发送SIGPROF
  • 结构体定义:
struct itimerval {
    struct timeval it_interval; // 周期间隔
    struct timeval it_value;    // 初始到期时间
};

3. sys/times.h(进程时间统计)

clock_t times(struct tms *buf);
  • 功能:获取进程及子进程的CPU时间统计
  • 结构体定义:
struct tms {
    clock_t tms_utime;  // 用户态CPU时间
    clock_t tms_stime;  // 内核态CPU时间
    clock_t tms_cutime; // 终止子进程的用户态CPU时间
    clock_t tms_cstime; // 终止子进程的内核态CPU时间
};

4. 使用场景对比

功能time.hsys/time.hsys/times.h
时间显示基础时间转换与格式化高精度时间获取(微秒级)不适用
间隔定时器POSIX定时器(纳秒级)传统定时器(微秒级)不适用
进程时间统计不适用不适用支持(times)
实时性支持支持(CLOCK_REALTIME)仅支持系统实时时钟不适用

5. 技术选型建议

  1. 基础时间操作
    • 优先使用time.h的strftime进行时间格式化
    • 跨平台场景可用localtime_r替代localtime(线程安全)
  2. 高精度计时
    • 首选clock_gettime(CLOCK_MONOTONIC)(精度达纳秒级)
    • 旧系统兼容场景使用gettimeofday(微秒级)
  3. 定时器实现
    • 实时系统:POSIX定时器(timer_create+timer_settime)支持多定时器独立管理
    • 传统场景:setitimer适合简单定时需求(需注意信号冲突问题)
  4. 性能监控
    • 进程级统计:sys/times.h的times函数
    • 代码段级分析:clock_gettime(CLOCK_THREAD_CPUTIME_ID)获取线程级CPU时间

引用说明

本文函数原型和实现原理参考了Linux手册页和POSIX标准,定时器示例代码可参考GitHub开源项目linux-timer-examples。

- 阅读全文 -
系统编程基础

使用GDB调试程序的完整指南

2025-03-21 浏览量 141 暂无评论

GDB调试命令终极指南:从基础到进阶

一、源码查看与分屏模式

  1. 查看源码片段
    • list [行号/函数名](简写l)显示指定位置的代码片段,默认每次展示10行。例如:

    (gdb) l main.c:20  # 显示main.c第20行附近的代码
    (gdb) l my_func    # 查看my_func函数的源码

    • 分屏模式:通过cgdb工具或gdb -tui命令启动分屏界面,上方显示源码,下方为调试命令窗口。按Esc切换代码屏,i返回调试屏。

或输入以下命令

       layout src  #以图形化窗口打开源码
       ctrl+x A    #关闭图形化窗口
  1. 源码路径指定
    若项目源码分散在多目录,使用directory <路径>添加搜索路径,show directories查看当前路径列表。

二、断点管理

  1. 设置断点
    • 基础断点:

    (gdb) b 30        # 第30行设置断点
    (gdb) b my_func   # 函数入口断点
    (gdb) b *0x400504 # 内存地址断点

    • 条件断点:break 45 if i==100,当变量i为100时触发。
    • 观察点:watch var监控变量值变化,rwatch var监控变量被读取。

  2. 断点操作
    • info break查看所有断点,delete <编号>删除断点。
    • disable/enable <编号>临时禁用/启用断点。

三、程序执行控制

  1. 运行与继续
    • run(简写r)启动程序,continue(c)继续执行至下一断点。
    • 单步执行:

    (gdb) next    # 逐过程(不进入函数)
    (gdb) step    # 逐语句(进入函数)
    (gdb) finish  # 执行完当前函数并返回
    (gdb) until 100 # 运行至第100行(跳出循环常用)
  2. 跳转与强制返回
    • jump 50跳转到第50行继续执行。
    • return <值>强制从当前函数返回指定值。

四、变量追踪与内存分析

  1. 查看变量
    • print var(p)显示变量值,ptype var查看类型。
    • 格式化输出:

    (gdb) p/x var   # 十六进制显示
    (gdb) p/d array@10 # 显示数组前10个元素
  2. 持续追踪
    • display var每次暂停时自动显示变量。
    • x/<格式> <地址>查看内存,如x/20xw 0x7fffffffe000显示20个4字节十六进制值。

五、堆栈分析

  1. 调用栈查看
    • backtrace(bt)显示完整调用栈,frame <编号>切换栈帧。
    • info frame查看当前栈帧的寄存器、局部变量等信息。
  2. 栈破坏调试
    结合info registers检查寄存器值,x命令分析内存是否越界。

六、多线程调试

  1. 线程管理
    • info threads显示所有线程,thread <ID>切换调试线程。
    • 线程锁定:

    (gdb) set scheduler-locking on  # 仅当前线程运行
    (gdb) set scheduler-locking step # 单步时锁定其他线程
  2. 死锁排查
    • 使用info mutex查看锁状态,在锁操作处设置条件断点。
    • 示例:break pthread_mutex_lock if mutex_id==0x1234。

七、大型项目调试技巧

  1. 附加到运行进程

    gdb attach <PID>       # 附加到正在运行的进程
    gdb -p <PID>           # 直接调试目标进程
  2. Core Dump分析
    • 生成Core文件:ulimit -c unlimited。
    • 加载分析:gdb program core,结合bt和info registers定位崩溃点。
  3. 宏调试
    编译时添加-g3选项,info macro MACRO_NAME查看宏定义。

八、高级命令速查表

场景命令示例
条件断点b 50 if i>100
监控指针解引用watch *(int*)0x7fffffffe3cc
多线程断点b func thread all
批量执行命令thread apply all bt
查看优化后变量set print frame-arguments all

通过上述命令组合,可高效应对C/C++项目的复杂调试场景。建议结合gdb -batch -x commands.gdb编写自动化调试脚本,提升效率。

- 阅读全文 -
系统编程基础

C语言可变参数与命令行参数解析:stdarg与getopt详解

2025-03-04 浏览量 138 暂无评论

C语言可变参数与命令行参数解析:stdarg与getopt详解


一、可变参数处理:stdarg.h

1.1 核心功能

stdarg.h 头文件提供在函数中处理不定数量参数的能力,常用于实现类似printf()的格式化输出函数。通过宏定义实现,与平台特性强关联。

1.2 核心类型与函数原型

#include <stdarg.h>

// 参数列表指针类型
va_list;

// 初始化va_list指针,last_param是最后一个固定参数名
void va_start(va_list ap, last_param);

// 获取下一个参数,type为参数类型(如int、char*)
// 一定要确定可变参数的个数,不要在最后一个参数被读取了依然调用该函数
type va_arg(va_list ap, type);

// 清理va_list指针
void va_end(va_list ap);

1.3 使用步骤

  1. 函数声明使用省略号(如int func(int n, ...))
  2. 定义va_list变量
  3. va_start初始化指针
  4. va_arg逐个读取参数
  5. va_end释放资源

1.4 示例:简化版printf函数实现

#include <stdarg.h>
#include <stdio.h>

void my_printf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);  // 初始化参数列表

    for (const char *p = fmt; *p != '\0'; p++) {
        if (*p != '%') {
            putchar(*p);  // 普通字符直接输出
            continue;
        }

        // 处理格式符 %
        switch (*++p) {    // 跳过%并读取下一个字符
            case 'd': {    // 处理整数
                int num = va_arg(ap, int);
                printf("%d", num);  // 借用标准库输出
                break;
            }
            case 's': {    // 处理字符串
                char *str = va_arg(ap, char*);
                printf("%s", str);
                break;
            }
            case 'c': {    // 处理字符
                char ch = va_arg(ap, int);  // char提升为int传递
                putchar(ch);
                break;
            }
            case '%': {    // 转义%符号
                putchar('%');
                break;
            }
            default: {     // 未知格式符
                putchar('?');
                break;
            }
        }
    }

    va_end(ap);  // 清理参数列表
}

// 调用示例:
// my_printf("ID:%d, Name:%s, Level:%c%%", 101, "Alice", 'A');
// 输出: ID:101, Name:Alice, Level:A%

1.5 注意事项

• 必须至少包含一个固定参数
• va_arg必须按实际类型调用(类型错误导致未定义行为)
• C99后支持va_copy复制参数列表


二、命令行参数解析:getopt()

2.1 核心功能

unistd.h中的getopt()函数提供命令行选项解析能力,支持带参数选项(如-f filename)、组合短选项(如-abc)等常见格式。

2.2 函数原型与全局变量

#include <unistd.h>

int getopt(int argc, char *const argv[],
           const char *optstring);

// 全局变量:
extern char *optarg;  // 当前选项的参数值
extern int optind;    // 下一个要处理的argv索引,在getopt返回-1时其值为第一个非选项或选项参数的索引
extern int optopt;    // 无效选项字符

选项字符串格式:

• 单个字符:表示无参数选项(如"a"对应-a)
• 字符后接冒号:必须带参数(如"f:"对应-f file)
• 字符后接双冒号:可选参数(非标准扩展,谨慎使用)

2.3 使用流程

int main(int argc, char **argv) {
    int opt;
    while ((opt = getopt(argc, argv, "hf:v")) != -1) {
        switch (opt) {
            case 'h':
                printf("Help info\n");
                break;
            case 'f':
                printf("File: %s\n", optarg); // 获取参数
                break;
            case 'v':
                printf("Version 1.0\n");
                break;
            case '?': // 未知选项
                printf("Unknown option: %c\n", optopt);
                break;
        }
    }
    // 处理剩余参数(非选项参数)
    for (int i = optind; i < argc; i++) {
        printf("Extra argument: %s\n", argv[i]);
    }
    return 0;
}

2.4 运行示例

$ ./demo -f config.txt -v input.txt
File: config.txt
Version 1.0
Extra argument: input.txt

2.5 注意事项

• 选项参数通过optarg获取
• 遇到非选项参数时停止解析(可用--强制结束选项解析)
• 重复调用getopt()继续解析时需重置optind = 1


三、总结对比

特性stdarggetopt
主要用途函数内部处理可变参数解析命令行选项
核心操作va_start/va_arg/va_end宏getopt()函数循环调用
典型场景自定义格式化输出、数学计算开发命令行工具
参数类型安全无(依赖开发者保证)选项字符预先定义
跨平台性标准C库支持POSIX系统支持(Windows需额外实现)

掌握这两个工具,能够显著提升C语言函数设计的灵活性和命令行工具的开发效率。实际使用时需注意参数类型匹配与错误处理,避免因类型错误导致程序崩溃。

- 阅读全文 -
系统编程基础

对底层IO的深度总结

2025-03-03 浏览量 132 评论数 1

对底层IO的深度总结

前言

本文介绍 stdio 库函数与系统调用 read/write 的核心行为,重点解析缓冲机制对 I/O 操作的影响。


一、read 系统调用

功能:原子地从内核缓冲区(内存)读取数据块到用户缓冲区(内存)。原子——多进程同时调用read也会保证每个进程读取到的数据块是完整的。

具体表格:

I/O类型是否启用O_NONBLOCK数据/状态条件read行为
标准输入否输入未就绪(如终端无输入)阻塞,直到输入数据或EOF
标准输入是输入未就绪立即返回EAGAIN错误
标准输入无所谓输入已就绪(用户键入回车读取实际输入字节数(≤请求字节数)
标准输入无所谓输入结束(如Ctrl+D)返回0(EOF)
普通文件无所谓文件未结束读取请求的字节数(除非文件剩余字节不足,返回实际剩余字节数)
普通文件无所谓已到文件结尾返回0(EOF)
管道/FIFO否管道中无数据,但写入端未关闭阻塞,直到有数据写入
管道/FIFO否管道中无数据,且写入端已关闭返回0(EOF)
管道/FIFO否管道中有数据(≤请求字节数)读取实际可用字节数
管道/FIFO是管道中无数据,但写入端未关闭立即返回EAGAIN错误
管道/FIFO是管道中有数据(≤请求字节数)读取实际可用字节数
套接字否无数据到达(面向连接,如TCP)阻塞,直到数据到达或连接关闭
套接字否连接关闭(如TCP收到FIN)返回0(EOF)
套接字否数据到达(≤请求字节数)读取实际到达字节数
套接字是无数据到达立即返回EAGAIN错误
套接字是数据到达(≤请求字节数)读取实际到达字节数

总结
read 何时阻塞:

  • 1.未设置O_NONBLOCK,
  • 2.对象是支持阻塞的设备(终端设备、套接字、管道等)而非普通文件,
  • 3.此时内核缓冲区恰好没有数据。

阻塞需同时满足上述三种情况。可以理解为read会等待可能提供数据的设备。其余情况,内核缓冲区一旦有数据块被write写入就一次性搬走并返回数据量,没读到就返回0(EOF/设备关闭)或-1(发生错误)。


二、write 系统调用

功能:原子地将用户缓冲区(内存)的数据块写入内核缓冲区(内存)。原子——多进程同时调用write也能保证每个进程写的数据块是独立的。

对象类型是否启用 O_NONBLOCK行为描述
标准输出未启用如果标准输出是终端设备,write 会阻塞,直到数据被读取或终端缓冲区满。
启用如果标准输出是终端设备,write 会立即返回。如果终端缓冲区满,返回 -1 并设置 errno 为 EAGAIN。
普通文件未启用write 会立即将数据写入文件,不会阻塞。
启用write 会立即将数据写入文件,不会阻塞。
管道/FIFO未启用如果写入字节数 n ≤ PIPE_BUF,write 会原子地写入n字节;如果 n > PIPE_BUF,write不能保证原子性。
启用如果写入字节数 n ≤ PIPE_BUF,write 会原子地写入n字节;如果 n > PIPE_BUF,返回 -1 并设置 errno 为 EAGAIN。
套接字未启用如果套接字缓冲区有足够空间,write 会立即写入;否则会阻塞,直到有足够空间。
启用如果套接字缓冲区有足够空间,write 会立即写入;否则返回 -1 并设置 errno 为 EAGAIN。
读取端关闭未启用如果写入对象是管道、FIFO 或套接字,且读取端关闭,write 会返回 -1 并设置 errno 为 EPIPE,同时可能会产生 SIGPIPE 信号。
启用如果写入对象是管道、FIFO 或套接字,且读取端关闭,write 会返回 -1 并设置 errno 为 EPIPE,同时可能会产生 SIGPIPE 信号。

关键点:

  • write 返回仅表示数据块已提交给内核,不保证已写入磁盘。
  • 阻塞模式下,若内核缓冲区满,write 会阻塞;非阻塞模式直接返回 EAGAIN。

三、stdio 库的缓冲机制

1. 输入类函数(如 scanf)

  • 何时调用 read:当用户缓冲区无足够数据时触发 read。
  • 缓冲影响:

    • 若有用户缓冲区:read 填充库缓冲区,数据按需提取(如 scanf 遇空格/换行停止)。
    • 若无缓冲区(_IONBF):read 直接填充用户指定内存。

示例对比:

// 例1:关闭缓冲区(_IONBF)
setvbuf(stdin, NULL, _IONBF, 0);
scanf("%s", string);  // read直接填充string,"world"留在内核缓冲区
// 例2:启用缓冲区(默认)
scanf("%s", string);  // read填充库缓冲区,"world"保留在库缓冲区中

2. 输出类函数(如 printf)

  • 何时调用 write:由缓冲模式决定:

    缓冲模式触发条件
    无缓冲(_IONBF)立即调用 write
    行缓冲(_IOLBF)遇到换行符或缓冲区满
    全缓冲(_IOFBF)缓冲区满或手动刷新(fflush)

示例对比:

// 行缓冲(默认)
printf("I'm A.\n");  // 遇换行符立即调用write
write(STDOUT_FILENO, "I'm B.\n", 7);  // 直接输出
// 输出顺序:A → B
// 全缓冲(_IOFBF)
printf("I'm A.\n");  // 缓冲区未满,不调用write
write(STDOUT_FILENO, "I'm B.\n", 7);  // 直接输出
// 输出顺序:B → A(程序退出时刷新缓冲区)

四、close的机制

对于进程打开一个文件而言,有三个关联的数据结构:进程层面的文件描述符表,内核层面的打开文件表,文件系统层面的inode表。
进程层面的文件描述符表记录了fd与fd指向的内核的打开文件表的文件描述(句柄);
内核层面的打开文件表的每个表项(即文件句柄)记录了当前文件IO操作的偏移量,以及文件在文件系统inode表中的位置(下标);
文件系统层面的inode表记录了每个文件的元数据,以及记录锁(系统调用fcntl()维护的POSIX锁)。

close(int fd)的作用就是断开文件描述符fd与文件描述(内核维护的打开文件表的表项)的指向关系,并在进程层面的文件描述符表删除这一表项。而内核层面的打开文件表的表项只有没有任何进程的文件描述符指向它的时候才会被删除。


总结

  • read/write 就是一个搬运者,可阻塞,也可立即返回。
  • stdio 封装了read与write,为用户进程建立缓冲区:

    • 输入类函数:缓冲决定数据暂存位置(库缓冲区或用户内存)。
    • 输出类函数:缓冲决定 write 的触发条件(换行符、缓冲区满或无缓冲)。

- 阅读全文 -
  1. 1
  2. 2
  3. 3
  4. 4

浏览量 : 5667

© 2025 It's Geek KingYoungy. Power By Typecho . Theme by Shiyi

浙ICP备2025160639号  |  浙公网安备33020502001222号

This is just a placeholder img.