3.makefile-gdb-文件IO

1.makefile

makefile文件中定义了一系列的规则来指定, 哪些文件需要先编译, 哪些文件需要后编译, 哪些文件需要重新编译, 甚至于进行更复杂的功能操作, 因为makefile就像一个Shell脚本一样, 其中也可以执行操作系统的命令. makefile带来的好处就是——“自动化编译”, 一旦写好, 只需要一个make命令, 整个工程完全自动编译, 极大的提高了软件开发的效率.
make是一个命令工具, 是一个解释makefile中指令的命令工具, 一般来说, 大多数的IDE都有这个命令, 比如:Visual C++的nmake, Linux下GNU的make. 可见, makefile都成为了一种在工程方面的编译方法.

makefile文件中会使用gcc编译器对源代码进行编译, 最终生成可执行文件或者是库文件.

1.1 makefile的基本规则

makefile基本规则三要素:
目标: 要生成的目标文件
依赖: 目标文件由哪些文件生成
命令: 通过执行该命令由依赖文件生成目标

下面以具体的例子来讲解:
当前目录下有main.c fun1.c fun2.c sum.c, 根据这个基本规则编写一个简单的makefile文件, 生成可执行文件main.

  • 第一个版本的makefile:
main:main.c fun1.c fun2.c sum.c
(Tab键)gcc -o main main.c fun1.c fun 2.csum.c

缺点: 效率低, 修改一个文件, 所有的文件会全部重新编译.

1.2 makefile工作原理

基本原则:
若想生成目标, 检查规则中的所有的依赖文件是否都存在:
如果有的依赖文件不存在, 则向下搜索规则, 看是否有生成该依赖文件的规则:
如果有规则用来生成该依赖文件, 则执行规则中的命令生成依赖文件;
如果没有规则用来生成该依赖文件, 则报错.
在这里插入图片描述

如果所有依赖都存在, 检查规则中的目标是否需要更新, 必须先检查它的所有依赖,依赖中有任何一个被更新, 则目标必须更新.(检查的规则是哪个时间大哪个最新)
若目标的时间 > 依赖的时间, 不更新
若目标的时间 < 依赖的时间, 则更新
在这里插入图片描述

总结:
分析各个目标和依赖之间的关系
根据依赖关系自底向上执行命令
根据依赖文件的时间和目标文件的时间确定是否需要更新
如果目标不依赖任何条件, 则执行对应命令, 以示更新(如:伪目标)

  • 第二个版本:
    在这里插入图片描述

缺点: 冗余, 若.c文件数量很多, 编写起来比较麻烦.

1.3 makefile中的变量

在makefile中使用变量有点类似于C语言中的宏定义, 使用该变量相当于内容替换, 使用变量可以使makefile易于维护, 修改起来变得简单。
makefile有三种类型的变量:

  • 普通变量
  • 自带变量
  • 自动变量

1.3.1普通变量

变量定义直接用 =
使用变量值用 $(变量名)
如:下面是变量的定义和使用
foo = abc // 定义变量并赋值
bar = $(foo) // 使用变量, $(变量名)
定义了两个变量: foo、bar, 其中bar的值是foo变量值的引用。

1.3.2 自带变量

除了使用用户自定义变量, makefile中也提供了一些变量(变量名大写)供用户直接使用, 我们可以直接对其进行赋值:

CC = gcc #arm-linux-gcc
CPPFLAGS : C预处理的选项 -I
CFLAGS:   C编译器的选项 -Wall -g -c
LDFLAGS :  链接器选项 -L  -l

1.3.3自动变量

$@: 表示规则中的目标
$<: 表示规则中的第一个条件
$^: 表示规则中的所有条件, 组成一个列表, 以空格隔开, 如果这个列表中有重复的项则消除重复项。
特别注意:自动变量只能在规则的命令中使用.

  • 模式规则
    至少在规则的目标定义中要包含’%’, ‘%’表示一个或多个, 在依赖条件中同样可以使用’%’, 依赖条件中的’%’的取值取决于其目标:
    比如: main.o:main.c fun1.o: fun1.c fun2.o:fun2.c, 说的简单点就是: xxx.o:xxx.c
    makefile的第三个版本:
    在这里插入图片描述

1.4 makefile函数

makefile中的函数有很多, 在这里给大家介绍两个最常用的。

  • 1.wildcard – 查找指定目录下的指定类型的文件
    src=$(wildcard *.c) //找到当前目录下所有后缀为.c的文件,赋值给src
  • 2.patsubst – 匹配替换
    obj=$(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o。在makefile中所有的函数都是有返回值的。

当前目录下有main.c fun1.c fun2.c sum.c
src= ( w i l d c a r d ∗ . c ) 等 价 于 s r c = m a i n . c f u n 1. c f u n 2. c s u m . c o b j = (wildcard *.c) 等价于src=main.c fun1.c fun2.c sum.c obj= (wildcard.c)src=main.cfun1.cfun2.csum.cobj=(patsubst %.c,%.o, $(src))等价于obj=main.o fun1.o fun2.o sum.o

makefile的第四个版本:
在这里插入图片描述
缺点: 每次重新编译都需要手工清理中间.o文件和最终目标文件

1.5 makefile的清理操作

用途: 清除编译生成的中间.o文件和最终目标文件
make clean 如果当前目录下有同名clean文件,则不执行clean对应的命令, 解决方案:
伪目标声明:
.PHONY:clean
声明目标为伪目标之后, makefile将不会检查该目标是否存在或者该目标是否需要更新
clean命令中的特殊符号:
“-”此条命令出错,make也会继续执行后续的命令。如:“-rm main.o”
rm -f: 强制执行, 比如若要删除的文件不存在使用-f不会报错
“@”不显示命令本身, 只显示结果。如:“@echo clean done”
其它
– make 默认执行第一个出现的目标, 可通过make dest指定要执行的目标
– make -f : -f执行一个makefile文件名称, 使用make执行指定的makefile: make -f mainmak
makefile的第5个版本:

在makefile的第5个版本中, 综合使用了变量, 函数, 模式规则和清理命令,
是一个比较完善的版本.

2 gdb调试

2.1 gdb介绍

GDB(GNU Debugger)是GCC的调试工具。其功能强大, 现描述如下:
GDB主要帮忙你完成下面四个方面的功能:

  1. 启动程序, 可以按照你的自定义的要求随心所欲的运行程序。
  2. 可让被调试的程序在你所指定的断点处停住。(断点可以是条件表达式)
  3. 当程序被停住时, 可以检查此时你的程序中所发生的事。
  4. 动态的改变你程序的执行环境。

2.2 生成调试信息

一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序, 首先在编译时, 我们必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如:

gcc -g hello.c -o hello

如果没有-g, 你将看不见程序的函数名、变量名, 所代替的全是运行时的内存地址。当你用-g把调试信息加入之后, 并成功编译目标代码以后, 让我们来看看如何用gdb来调试他。

2.3 启动gdb

  • 启动gdb:gdb program
    program 也就是你的执行文件, 一般在当前目录下。
  • 设置运行参数
    set args 可指定运行时参数。(如:set args 10 20 30 40 50 )
    show args 命令可以查看设置好的运行参数。
  • 启动程序
    run:程序开始执行, 如果有断点, 停在第一个断点处
    start:程序向下执行一行。(在第一条语句处停止)

2.4 显示源代码

GDB 可以打印出所调试程序的源代码, 当然, 在程序编译时一定要加上-g的参数, 把源程序信息编译到执行文件中。不然就看不到源程序了。当程序停下来以后, GDB会报告程序停在了那个文件的第几行上。你可以用list命令来打印程序的源代码, 默认打印10行, list命令的用法如下所示:

list linenum:打印第linenum行的上下文内容.
list function:显示函数名为function的函数的源程序。
list: 显示当前行后面的源程序。
list -:显示当前文件开始处的源程序。
list file:linenum: 显示file文件下第n行
list file:function: 显示file文件的函数名为function的函数的源程序

一般是打印当前行的上5行和下5行, 如果显示函数是是上2行下8行, 默认是10行, 当然, 你也可以定制显示的范围, 使用下面命令可以设置一次显示源程序的行数。

set listsize count:设置一次显示源代码的行数。         
show listsize:   查看当前listsize的设置。

2.5 设置断点

  • 简单断点—当前文件
    break 设置断点, 可以简写为b
    b 10 设置断点, 在源程序第10行
    b func 设置断点, 在func函数入口处
  • 多文件设置断点—其他文件
    在进入指定函数时停住:
    b filename:linenum --在源文件filename的linenum行处停住
    b filename:function --在源文件filename的function函数的入口处停住
  • 查询所有断点
    info b == info break == i break == i b
    条件断点
    一般来说, 为断点设置一个条件, 我们使用if关键词, 后面跟其断点条件。设置一个条件断点:
    b test.c:8 if intValue == 5
  • 维护断点
    delete [range…] 删除指定的断点, 其简写命令为d。
    如果不指定断点号, 则表示删除所有的断点。range表示断点号的范围
    删除某个断点: delete num
    删除多个断点: delete num1 num2 …
    删除连续的多个断点: delete m-n
    删除所有断点: delete
    比删除更好的一种方法是disable停止点, disable了的停止点, GDB不会删除, 当你还需要时, enable即可, 就好像回收站一样。
    disable [range…] 使指定断点无效, 简写命令是dis。

如果什么都不指定, 表示disable所有的停止点。
使一个断点无效/有效: disable num
使多个断点无效有效: disable num1 num2 …
使多个连续的断点无效有效: disable m-n
使所有断点无效有效: disable
enable [range…] 使无效断点生效, 简写命令是ena。

如果什么都不指定, 表示enable所有的停止点。
使一个断点无效/有效: enable num
使多个断点无效有效: enable num1 num2 …
使多个连续的断点无效有效: enable m-n
使所有断点无效有效: disable/enable

2.6 调试代码

  • run 运行程序, 可简写为r
  • next 单步跟踪, 函数调用当作一条简单语句执行, 可简写为n
  • step 单步跟踪, 函数调进入被调用函数体内, 可简写为s
  • finish 退出进入的函数, 如果出不去, 看一下函数体中的循环中是否有断点,如果有删掉,或者设置无效
  • until 在一个循环体内单步跟踪时, 这个命令可以运行程序直到退出循环体,可简写为u,如果出不去, 看一下函数体中的循环中是否有断点,如果有删掉,或者设置无效
  • continue 继续运行程序, 可简写为c(若有断点则跳到下一个断点处)

2.7 查看变量的值

  • 查看运行时变量的值
    print 打印变量、字符串、表达式等的值, 可简写为p
    p count -----打印count的值
  • 自动显示变量的值
    你可以设置一些自动显示的变量, 当程序停住时, 或是在你单步跟踪时, 这些变量会自动显示。相关的GDB命令是display。
  • display 变量名
    info display – 查看display设置的自动显示的信息。
    undisplay num(info display时显示的编号)
    delete display dnums… – 删除自动显示, dnums意为所设置好了的自动显式的编号。如果要同时删除几个, 编号可以用空格分隔, 如果要删除一个范围内的编号, 可以用减号表示(如:2-5)
    删除某个自动显示: undisplay num 或者delete display num
    删除多个: delete display num1 num2
    删除一个范围: delete display m-n
  • disable display dnums…
    使一个自动显示无效: disable display num
    使多个自动显示无效: delete display num1 num2
    使一个范围的自动显示无效: delete display m-n
  • enable display dnums…
    使一个自动显示有效: enable display num
    使多个自动显示有效: enable display num1 num2
    使一个范围的自动显示有效: enable display m-n
    disable和enalbe不删除自动显示的设置, 而只是让其失效和恢复。
    查看修改变量的值
    ptype width --查看变量width的类型
    type = double
    p width --打印变量width 的值
    $4 = 13
    你可以使用set var命令来告诉GDB, width不是你GDB的参数, 而是程序的变量名, 如:
    set var width=47 // 将变量var值设置为47
    在你改变程序变量取值时, 最好都使用set var格式的GDB命令。

3.文件IO

从本章开始学习各种Linux系统函数, 这些函数的用法必须结合Linux内核的工作原理来理解, 因为系统函数正是内核提供给应用程序的接口, 而要理解内核的工作原理, 必须熟练掌握C语言, 因为内核也是用C语言写的, 我们在描述内核工作原理时必然要用“指针”、“结构体”、“链表”这些名词来组织语言, 就像只有掌握了英语才能看懂英文书一样, 只有学好了C语言才能看懂我描述的内核工作原理。

3.1 C库IO函数的工作流程

在这里插入图片描述
在这里插入图片描述

c语言操作文件相关问题:
使用fopen函数打开一个文件, 返回一个FILE* fp, 这个指针指向的结构体有三个重要的成员.

  • 文件描述符: 通过文件描述可以找到文件的inode, 通过inode可以找到对应的数据块
  • 文件指针: 读和写共享一个文件指针, 读或者写都会引起文件指针的变化
  • 文件缓冲区: 读或者写会先通过文件缓冲区, 主要目的是为了减少对磁盘的读写次数, 提高读写磁盘的效率.

备注:

1.头文件stdio.h 的第48行处: typedef struct _IO_FILE FILE;
2.头文件libio.h 的第241行处: struct _IO_FILE, 这个接头体定
义中有一个_fileno成员, 这个就是文件描述符

3.2 C库函数与系统函数的关系

在这里插入图片描述

系统调用: 由操作系统实现并提供给外部应用程序的编程接口,
(Application Programming Interface, API), 是应用程序同系统之间数据交互的桥梁.

3.3 虚拟地址空间

在这里插入图片描述

进程的虚拟地址空间分为用户区和内核区, 其中内核区是受保护的, 用户是不能够对其进行读写操作的;
内核区中很重要的一个就是进程管理, 进程管理中有一个区域就是PCB(本质是一个结构体);
PCB中有文件描述符表, 文件描述符表中存放着打开的文件描述符, 涉及到文件的IO操作都会用到这个文件描述符.

3.4 pcb和文件描述符表

在这里插入图片描述
备注:
pcb:结构体:task_stuct, 该结构体在:
/usr/src/linux-headers-4.4.0-97/include/linux/sched.h:1390
一个进程有一个文件描述符表:1024
前三个被占用, 分别是STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO
文件描述符作用:通过文件描述符找到inode, 通过inode找到磁盘数据块.

虚拟地址空间内核区PCB文件描述表文件描述符文件IO操作使用文件描述符

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值