当前位置: 首页 > news >正文

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类似,只是提示不一样。

http://www.jsqmd.com/news/648163/

相关文章:

  • 青少年软编等考五级题解目录
  • AutoSAR ETH Driver集成LwIP:Tc3XX平台下接收中断与发送缓冲区的配置与调试指南
  • 小红书博主必看:AI智能体如何5分钟搞定高颜值封面+3张内容页(附保姆级教程)
  • VPet存档迁移终极指南:如何快速升级旧版本数据到v2格式
  • python-gitlab CLI 工具深度解析:30个常用命令让 GitLab 管理变得简单
  • Ansible之Playbook(六):实例部署实战
  • MQTT over WebSocket实战指南:从EMQX安装到消息收发全流程
  • 该贴已作废
  • 告别深度依赖:手把手拆解BEVFormer如何用Transformer实现纯视觉BEV感知
  • 旋风分离器几何建模避坑指南:Star CCM+中布尔运算的5个常见错误
  • DeepSeek LeetCode 1434.每个个戴不同帽子的方案数 public int numberWays(List<List<Integer>> hats)
  • 从‘看图说话’到‘看截图答题’:MMMU-Pro如何模拟真实用户场景来‘拷问’AI?
  • Vue3 项目集成 OnlyOffice 在线编辑 + 自定义插件开发(一)
  • DeepSeek LeetCode 1439. 有序矩阵中的第 k 个最小数组和 public int kthSmallest(int[][] mat, int k)
  • Python 装饰器高级应用指南
  • 手把手教你用DigNet从scRNA-seq数据构建基因调控网络(附乳腺癌案例解析)
  • PyTorch 2.8镜像高清案例:Stable Video Diffusion生成电影级运镜视频截图
  • 玻璃幕墙的自爆原因,以及安全隐患分析
  • GeographicLib 地理计算库终极指南:从WMM2025地磁模型到高精度坐标转换实战
  • c++ string字符串详解
  • 渗透测试中的优先级选择:以Misdirection靶机为例解析如何避免死磕
  • IndexTTS-2-LLM与Tacotron2对比:新一代TTS优势分析
  • DeepSeek linux-6.19/net/ipv6/addrconf.c 源码分析
  • 2025_NIPS_MASTER: Enhancing Large Language Model via Multi-Agent Simulated Teaching
  • 从Word2Vec到BERT:前馈网络(FFNN)在NLP预训练模型里扮演了什么角色?
  • 深入理解Millennium的FFI机制:TypeScript与Lua的完美交互
  • 未来5年最“钱“景岗位揭晓:AI产品经理,普通人如何从0到1逆袭?(内含3步进阶法+学习资源)
  • 2025_NIPS_HyperMARL: Adaptive Hypernetworks for Multi-Agent RL
  • Windows 10/11网络配置全攻略:手把手教你修改IPv4地址(含子网掩码自动计算)
  • 「游戏史话第1期」莉莉丝的远征:从“差评”打工人,到狂揽百亿的出海领军者