TLPI 第3章 练习:System Programming Concepts

张开发
2026/4/16 3:36:13 15 分钟阅读

分享文章

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// -stdc99编译器遵循 C99 标准ISO/IEC 9899:1999// _XOPEN_SOURCE600按照 X/Open 第 6 期规范即 SUSv3暴露接口// -D_DEFAULT_SOURCE暴露默认的、传统的 UNIX/POSIX 接口以及常见的扩展。// -g包含调试信息IMPL_CFLAGS-stdc99-D_XOPEN_SOURCE600\-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}LDLIBSRMrm-f.PHONY:all allgen showall clean以下为fileio目录中的Makefile// 包含上级目录的公共配置编译器、标志、库路径等include../Makefile.inc// 分类列出需要编译的程序名GEN_EXEatomic_append bad_exclusive_open copy \ multi_descriptors seek_io t_readv t_truncate LINUX_EXElarge_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_EXEex3-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#includefcntl.h#includetlpi_hdr.hintmain(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调试程序。简单示例程序#includestdio.hintadd(inta,intb){intcab;returnc;}intmain(){intx3;inty5;intzadd(x,y);printf(z %d\n,z);return0;}如果还想演示数组、指针、段错误可以用这个稍复杂的版本#includestdio.h#includestdlib.hvoidinit_array(int*arr,intn){for(inti0;in;i){arr[i]i*10;}}intmain(){int*pNULL;intarr[5];init_array(arr,5);for(inti0;i5;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]nameOracle Linux$releaseverDebuginfo Packagesbaseurlhttps://oss.oracle.com/ol$releasever/debuginfo/gpgkeyfile:///etc/pki/rpm-gpg/RPM-GPG-KEY-oraclegpgcheck1enabled1sudoyuminstall-yglibc-debuginfogdb帮助(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.Typehelpfollowed by a class namefora list of commands in that class.Typehelp allforthe list of all commands.Typehelpfollowed by command nameforfull documentation.Typeapropos wordto searchforcommands related toword.Typeapropos -v wordforfull documentation of commands related toword.Command name abbreviations are allowedifunambiguous.先来看runningclass中的常用命令step: 下一行源代码遇到函数会进入类似于step innext: 下一行源代码但不会进入函数类似于step overfinish: 执行完当前函数返回调用者类似于step outuntil: 不带参数时等于next带参数n时为运行到指定行nstart: 启动调试程序并停留在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);重点了解getIntgetLong是类似的。测试程序如下// ex3-2.c#includetlpi_hdr.hvoidgetInt_test(constchar*src,intflags,constchar*msg);intmain(){char*s20;getInt_test(s,0,base 10);getInt_test(s,GN_BASE_8,base 8);getInt_test(s,GN_BASE_16,base 16);s0x20;getInt_test(s,GN_ANY_BASE,base any);s020;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;agetInt(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中的报错函数。terminateNORETURNstaticvoidterminate(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) ...outputErrorstaticvoidoutputError(Boolean useErr,interr,Boolean flushStdout,constchar*format,va_list ap)useErr控制是否打印err对应的错误字符串如EPERM。然后调用了vsnprintf和snprintf。errMsgvoiderrMsg(constchar*format,...)errMsg调用了outputError并在调用前后保存和恢复了errno。errExiterrExit依次调用了outputError和terminate。err_exiterr_exit与errExit的唯一区别为是否冲刷标准输出。errExitENerrExitEN与errExit的唯一区别为前者通过errnum指定了errno而后者使用默认的errno。fatal不打印errno错误信息直接退出。usageErr打印程序的帮助信息到标准错误。cmdLineErr与usageErr类似只是提示不一样。

更多文章