news 2026/4/16 3:36:11

TLPI 第3章 练习:System Programming Concepts

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TLPI 第3章 练习:System Programming Concepts

笔记和练习博客总目录见:开始读TLPI。

练习3-1来自原书,其余为自编练习。

练习3-1

在使用 Linux 特定的 reboot() 系统调用来重启系统时,第二个参数 magic2 必须指定为一组魔数之一(例如,LINUX_REBOOT_MAGIC2)。这些数字的意义是什么?(将它们转换为十六进制会提供一些线索。)

回答:
首先魔数的目的是在权限校验外再加一层数值校验,因为reboot是高危操作。而magic的值必须为0xfee1dead,实际就是feel dead的英文。magic2的值如下:

#defineLINUX_REBOOT_MAGIC2672274793#defineLINUX_REBOOT_MAGIC2A85072278#defineLINUX_REBOOT_MAGIC2B369367448#defineLINUX_REBOOT_MAGIC2C537993216

printf "0x%x\n"转换为16进制后为,可以看出其为日期:

0x281219690x51219960x160419980x20112000

而第一个日期是Linus Torvalds的生日。另外三个是他三个女儿的生日。

练习3-2

探索随书示例代码中的Makefile

在根目录中的Makefile.inc,是所有 makefile 使用的常用定义。以下中文为注释:

TLPI_DIR=..TLPI_LIB=${TLPI_DIR}/libtlpi.a TLPI_INCL_DIR=${TLPI_DIR}/lib LINUX_LIBRT=-lrt LINUX_LIBDL=-ldl LINUX_LIBACL=-lacl LINUX_LIBCRYPT=-lcrypt LINUX_LIBCAP=-lcap// -std=c99,编译器遵循 C99 标准(ISO/IEC 9899:1999)// _XOPEN_SOURCE=600,按照 X/Open 第 6 期规范(即 SUSv3)暴露接口// -D_DEFAULT_SOURCE,暴露默认的、传统的 UNIX/POSIX 接口,以及常见的扩展。// -g,包含调试信息IMPL_CFLAGS=-std=c99-D_XOPEN_SOURCE=600\-D_DEFAULT_SOURCE \-g-I${TLPI_INCL_DIR}\-pedantic \-Wall \-W \-Wmissing-prototypes \-Wimplicit-fallthrough \-Wno-unused-parameterifeq($(CC),clang)IMPL_CFLAGS+=-Wno-uninitialized-Wno-infinite-recursion \-Wno-format-pedantic endif CFLAGS=${IMPL_CFLAGS}IMPL_THREAD_FLAGS=-pthread IMPL_LDLIBS=${TLPI_LIB}LDLIBS=RM=rm-f.PHONY:all allgen showall clean

以下为fileio目录中的Makefile:

// 包含上级目录的公共配置(编译器、标志、库路径等)include../Makefile.inc// 分类列出需要编译的程序名GEN_EXE=atomic_append bad_exclusive_open copy \ multi_descriptors seek_io t_readv t_truncate LINUX_EXE=large_file// 定义不同构建任务EXE=${GEN_EXE}${LINUX_EXE}all:${EXE}allgen:${GEN_EXE}clean:${RM}${EXE}*.o showall:@ echo ${EXE}// 声明所有可执行文件依赖于 TLPI 辅助库${EXE}:${TLPI_LIB}# True as a rough approximation

练习3-3

编写一个利用TLPI库的程序,编译并运行

创建练习目录ex,并从某一子目录中拷贝Makefile文件,并稍作修改:

mkdirexcdexcp../fileio/Makefile.# 修改Makefile,主要是编译目标,即GEN_EXE

修改后的Makefile如下:

include../Makefile.inc GEN_EXE=ex3-1EXE=${GEN_EXE}all:${EXE}allgen:${GEN_EXE}clean:${RM}${EXE}*.o showall:@ echo ${EXE}${EXE}:${TLPI_LIB}# True as a rough approximation

编写示例程序ex3-1.c:

#include<fcntl.h>#include"tlpi_hdr.h"intmain(intargc,char*argv[]){usageErr("%s old-file new-file\n",argv[0]);exit(EXIT_SUCCESS);}

编译并运行:

$ make $./ex3-1Usage:./ex3-1old-file new-file

练习3-4

熟悉gdb调试程序。

简单示例程序:

#include<stdio.h>intadd(inta,intb){intc=a+b;returnc;}intmain(){intx=3;inty=5;intz=add(x,y);printf("z = %d\n",z);return0;}

如果还想演示数组、指针、段错误,可以用这个稍复杂的版本:

#include<stdio.h>#include<stdlib.h>voidinit_array(int*arr,intn){for(inti=0;i<n;i++){arr[i]=i*10;}}intmain(){int*p=NULL;intarr[5];init_array(arr,5);for(inti=0;i<5;i++){printf("arr[%d] = %d\n",i,arr[i]);}// uncomment to demo segment error// *p = 100;return0;}

编译:

$ cc-g a.c

调试:

gdb a.out

启动调试后,有警告如下,可忽略:

Missing rpms,try:dnf--enablerepo='*debug*'install glibc-debuginfo-2.34-231.0.1.el9_7.10.x86_64

调试信息有两者存储方式,一个是和源程序存放在一起,一个是分离存储,即debuginfo形式。因为我有源码,所以debuginfo暂用不上。不过若你一定要安装,可以:

# for Oracle Linux 9sudovi/etc/yum.repos.d/debuginfo.repo# content of debuginfo.repo[debuginfo]name=Oracle Linux$releaseverDebuginfo Packagesbaseurl=https://oss.oracle.com/ol$releasever/debuginfo/gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oraclegpgcheck=1enabled=1sudoyuminstall-yglibc-debuginfo

gdb帮助:

(gdb)help List of classes of commands:aliases--User-defined aliases of other commands.breakpoints--Making program stop at certain points.data--Examining data.files--Specifying and examining files.internals--Maintenance commands.obscure--Obscure features.running--Running the program.stack--Examining the stack.status--Status inquiries.support--Support facilities.text-user-interface--TUI is the GDB text based interface.tracepoints--Tracing of program execution without stopping the program.user-defined--User-defined commands.Type"help"followed by a class namefora list of commands in that class.Type"help all"forthe list of all commands.Type"help"followed by command nameforfull documentation.Type"apropos word"to searchforcommands related to"word".Type"apropos -v word"forfull documentation of commands related to"word".Command name abbreviations are allowedifunambiguous.

先来看runningclass中的常用命令:

  • step: 下一行源代码,遇到函数会进入,类似于step in
  • next: 下一行源代码,但不会进入函数,类似于step over
  • finish: 执行完当前函数,返回调用者,类似于step out
  • until: 不带参数时等于next,带参数n时为运行到指定行n
  • start: 启动调试程序,并停留在main函数第一行
  • continue: 在信号或断点后继续调试程序。

breakpointsclass中的常用命令:

  • break:设置断点于某行或某函数
  • info break:显示断点
  • watch:为表达式设置观察点。

dataclass中的常用命令:

  • print:打印变量

stackclass中的常用命令:

  • where:当前运行位置

fileclass中的常用命令:

  • list:列出指定的函数或行。

练习3-5

熟悉TLPI库中的数值转换函数。详见原文第58页。

这两个函数定义在文件lib/get_num.c中:

intgetInt(constchar*arg,intflags,constchar*name);longgetLong(constchar*arg,intflags,constchar*name);

重点了解getInt,getLong是类似的。

测试程序如下:

// ex3-2.c#include"tlpi_hdr.h"voidgetInt_test(constchar*src,intflags,constchar*msg);intmain(){char*s="20";getInt_test(s,0,"base 10");getInt_test(s,GN_BASE_8,"base 8");getInt_test(s,GN_BASE_16,"base 16");s="0x20";getInt_test(s,GN_ANY_BASE,"base any");s="020";getInt(s,GN_ANY_BASE,"base any");s="-20";getInt(s,GN_NONNEG,"non negative");exit(EXIT_SUCCESS);}voidgetInt_test(constchar*src,intflags,constchar*msg){inta;a=getInt(src,flags,msg);printf("source is %s, %s result is %d\n",src,msg,a);}

运行如下:

$ ./ex3-2sourceis20, base10result is20sourceis20, base8result is16sourceis20, base16result is32sourceis 0x20, base any result is32getInt error(in non negative): negative value not allowed offending text:-20

由于getInt是strtol的wrapper,所以需要重点了解strtol。

longstrtol(constchar*restrict nptr,char**restrict endptr,intbase);

strtol将字符串转换为long integer。但他只在溢出时和base非法时报错,如果是不正确的输入,如十进制转换“abc”时,返回值为0,且errno并不会发生变化,具体的错误需要根据endptr来判断,*endptr指向第一个非法字符。

所以我们理解了,使用这些函数而不是 atoi()、atol() 和 strtol() 的主要优点是它们对数字参数提供了一些基本的有效性检查,但他们的行为特征是一旦出错,则退出程序。

练习3-6

熟悉lib/error_functions.c中的报错函数。

terminate:

NORETURNstaticvoidterminate(Boolean useExit3)

若环境变量EF_DUMPCORE非空,则调用abort()退出并生成core dump。否则根据参数设置调用exit或_exit。

abort(3)中并没有提到core dump,这是信号SIGABRT的默认行为,详见signal(7):

Core Default action is to terminate the process and dump core (see core(5)). ... Signal Standard Action Comment ──────────────────────────────────────────────────────────────────────── SIGABRT P1990 Core Abort signal from abort(3) ...

outputError

staticvoidoutputError(Boolean useErr,interr,Boolean flushStdout,constchar*format,va_list ap)

useErr控制是否打印err对应的错误字符串,如"EPERM"。

然后调用了vsnprintf和snprintf。

errMsg

voiderrMsg(constchar*format,...)

errMsg调用了outputError,并在调用前后保存和恢复了errno。

errExit
errExit依次调用了outputError和terminate。

err_exit
err_exit与errExit的唯一区别为:是否冲刷标准输出。

errExitEN
errExitEN与errExit的唯一区别为:前者通过errnum指定了errno,而后者使用默认的errno。

fatal
不打印errno错误信息,直接退出。

usageErr
打印程序的帮助信息到标准错误。

cmdLineErr
与usageErr类似,只是提示不一样。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 3:34:16

【人工智能】OpenClaw Gateway 进程托管实践:从裸奔到 PM2 标准化管理

一、问题背景 1.1 故障现象 在一次定期服务检查中,发现: Gateway 端口 5000 无法访问 openclaw gateway probe 返回 ECONNREFUSED 127.0.0.1:5000 进程列表中找不到 openclaw-gateway 服务完全中断,需要人工登录服务器手动重启 1.2 原有启动方式 原先使用简单的 shell 脚…

作者头像 李华
网站建设 2026/4/16 3:32:31

STM32-八位流水灯进阶:定时器与循环延时的对比实现

1. 从流水灯入门到进阶&#xff1a;两种延时方式的本质区别 刚开始接触STM32开发时&#xff0c;流水灯实验就像是一道必经的入门仪式。我清楚地记得自己第一次用循环延时实现流水灯时的兴奋感&#xff0c;但后来发现这种简单粗暴的方式在实际项目中存在不少问题。直到学会了定时…

作者头像 李华
网站建设 2026/4/16 3:31:20

2026朱雀AI检测率高用什么降?首推嘎嘎降AI

2026朱雀AI检测率高用什么降&#xff1f;首推嘎嘎降AI 2026年毕业季已经到了&#xff0c;AIGC检测这道关比去年又严了不少。身边不少同学跟我反映&#xff0c;论文在朱雀上检测出来AI率40%、50%甚至更高&#xff0c;急得焦头烂额。 “到底用什么工具能把朱雀AI检测率降下来&…

作者头像 李华
网站建设 2026/4/16 3:23:12

2026年工程AI动画框架:USD+知识图谱新标准

针对“2026年工程AI动画框架是否会形成基于USD知识图谱的统一语义标准”这一问题&#xff0c;答案是&#xff1a;到2026年&#xff0c;形成全球性、强制性的统一标准可能性较低&#xff0c;但以USD为几何与场景描述基础、以领域知识图谱为语义增强与推理层的“事实性”融合架构…

作者头像 李华