目录
1 makefile
1.1 makefile的基本规则
1.2 makefile工作原理
1.3 makefile中的变量
1.4 makefile函数
1.5 makefile的清理操作
2 gdb调试
2.1 gdb介绍
2.2 生成调试信息
2.3 启动gdb
2.4 显示源代码
3文件IO
3.1 C库IO函数的工作流程
3.2 C库函数与系统函数的关系
3.3 虚拟地址空间
3.4 pcb和文件描述符表
学习目标:
熟练使用规则编写简单的makefile文件
熟练使用makefile中的变量
熟练使用makefile中的函数
熟练掌握gdb相关调试命令的使用
了解概念: pcb和文件描述符,虚拟地址空间
熟练掌握Linux系统IO函数的使用
makefile文件中定义了一系列的规则来指定, 哪些文件需要先编译, 哪些文件需要后编译, 哪些文件需要重新编译, 甚至于进行更复杂的功能操作, 因为makefile就像一个Shell脚本一样, 其中也可以执行操作系统的命令. makefile带来的好处就是——“自动化编译”, 一旦写好, 只需要一个make命令, 整个工程完全自动编译, 极大的提高了软件开发的效率.
make是一个命令工具, 是一个解释makefile中指令的命令工具, 一般来说, 大多数的IDE都有这个命令, 比如:Visual C++的nmake, Linux下GNU的make. 可见, makefile都成为了一种在工程方面的编译方法.
makefile文件中会使用gcc编译器对源代码进行编译, 最终生成可执行文件或者是库文件.
makefile文件的命名:makefile或者Makefile。
makefile由一组规则组成,规则如下:
目标: 依赖
(tab)命令
makefile基本规则三要素:
下面以具体的例子来讲解:
当前目录下有main.c fun1.c fun2.c sum.c, 根据这个基本规则编写一个简单的makefile文件, 生成可执行文件main.
main.c
#include
#include
#include
#include
#include
#include "head.h"
int main(int argc, char *argv[])
{
printf("this is main!\n");
int i = 0;
for(i=0; i
head.h
void fun1();
void fun2();
int sum(int len);
func1.c
#include
void fun1()
{
printf("this is fun1\n");
}
func2.c
#include
int sum(int len)
{
int i = 0;
int sum = 0;
for(i=0; i
makefile基本规则三要素:
下面以具体的例子来讲解:
当前目录下有main.c fun1.c fun2.c sum.c, 根据这个基本规则编写一个简单的makefile文件, 生成可执行文件main
第一个版本的makefile:
main:main.c func1.c func1.c sum.c
gcc -o main main.c func1.c func2.c sum.c -I ./
缺点: 效率低, 修改一个文件, 所有的文件会全部重新编译.
基本原则:
如果有规则用来生成该依赖文件, 则执行规则中的命令生成依赖文件;
如果没有规则用来生成该依赖文件, 则报错.
总结:
检查规则:
要想生成目标文件, 先要检查依赖条件是否都存在:
若都存在, 则比较目标时间和依赖的时间, 如果依赖的时候比目标的时间新,
则重新生成目标; 否则不重新生成
若不存在, 则往下找有没有生成依赖的规则, 有则生成, 如果没有则报错.
第二个版本:
main:main.o func1.o func2.o sum.o
gcc -o main main.o func1.o func2.o sum.o
main.o:main.c
gcc -c main.c -I./
func1.o:func1.c
gcc -c func1.c
func2.o:func2.c
gcc -c func2.c
sum.o:sum.c
gcc -c sum.c
缺点: 冗余, 若.c文件数量很多, 编写起来比较麻烦.
在makefile中使用变量有点类似于C语言中的宏定义, 使用该变量相当于内容替换, 使用变量可以使makefile易于维护, 修改起来变得简单。
makefile有三种类型的变量:
普通变量
如:下面是变量的定义和使用
foo = abc // 定义变量并赋值
bar = $(foo) // 使用变量, $(变量名)
定义了两个变量: foo、bar, 其中bar的值是foo变量值的引用。除了使用用户自定义变量, makefile中也提供了一些变量(变量名大写)供用户直接使用, 我们可以直接对其进行赋值:
CC = gcc #arm-linux-gcc
CPPFLAGS : C预处理的选项 -I
CFLAGS: C编译器的选项 -Wall -g -c
LDFLAGS : 链接器选项 -L -l
特别注意:自动变量只能在规则的命令中使用.
模式规则
至少在规则的目标定义中要包含’%’, ‘%’表示一个或多个, 在依赖条件中同样可以使用’%’, 依赖条件中的’%’的取值取决于其目标:
比如: main.o:main.c fun1.o: fun1.c fun2.o:fun2.c, 说的简单点就是: xxx.o:xxx.c
makefile的第三个版本:
target=main
objects=main.o func1.o func2.o sum.o
CC=gcc
CPPFLAGS=-I./
$(target):$(objects)
$(CC) -o $@ $^ #this is mingling
%.o:%.c
$(CC) -c $< $(CPPFLAGS)
# main.o:main.c
# $(CC) -c $< $(CPPFLAGS)
# func1.o:func1.c
# $(CC) -c $<
# func2.o:func2.c
# $(CC) -c $<
# sum.o:sum.c
# $(CC) -c $<
变量:
自定义变量: var = hello, $(var)
自带变量: CC CPPFLAGS CFLAGS LDFLAGS
自动变量: $@ $< $^
模式规则:
%.o:%.c------> 前后的%必须是相同
makefile中的函数有很多, 在这里给大家介绍两个最常用的。
- wildcard – 查找指定目录下的指定类型的文件
src=$(wildcard *.c) //找到当前目录下所有后缀为.c的文件,赋值给src
- patsubst – 匹配替换
obj=$(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o
在makefile中所有的函数都是有返回值的。
当前目录下有main.c fun1.c fun2.c sum.c
src=$(wildcard *.c) 等价于src=main.c fun1.c fun2.c sum.c
obj=$(patsubst %.c,%.o, $(src))等价于obj=main.o fun1.o fun2.o sum.o
makefile的第四个版本:
target=main
src=$(wildcard *.c)
objects=$(patsubst %.c,%.o,$(src))
CC=gcc
CPPFLAGS=-I./
$(target):$(objects)
$(CC) -o $@ $^
%.o:%.c
$(CC) -c $< $(CPPFLAGS)
缺点: 每次重新编译都需要手工清理中间.o文件和最终目标文件
用途: 清除编译生成的中间.o文件和最终目标文件
make clean 如果当前目录下有同名clean文件,则不执行clean对应的命令, 解决方案:
.PHONY:clean
- 声明目标为伪目标之后, makefile将不会检查该目标是否存在或者该目标是否需要更新
- clean命令中的特殊符号:
rm -f: 强制执行, 比如若要删除的文件不存在使用-f不会报错
- “@”不显示命令本身, 只显示结果。如:“@echo clean done”
- 其它
– make 默认执行第一个出现的目标, 可通过make dest指定要执行的目标
– make -f : -f执行一个makefile文件名称, 使用make执行指定的makefile: make -f mainmak
makefile的第5个版本:
target=main
src=$(wildcard *.c)
objects=$(patsubst %.c,%.o,$(src))
CC=gcc
CPPFLAGS=-I./
$(target):$(objects)
$(CC) -o $@ $^
%.o:%.c
$(CC) -c $< $(CPPFLAGS)
.PHONY:clean
clean:
rm -f $(objects) $(target)
多个makefile 可以将之前的makefile 起一个别名,如mainmake 此时使用
make -f mainmake 即可
在makefile的第5个版本中, 综合使用了变量, 函数, 模式规则和清理命令, 是一个比较完善的版本.
增加清理功能.
终极目标: makefile文件中第一次出现的目标叫做终极目标
.PHONY:clean
clean:
rm -f ....
使用-f可以指定makefile文件, 如: make -f mainmak
GDB(GNU Debugger)是GCC的调试工具。其功能强大, 现描述如下:
GDB主要帮忙你完成下面四个方面的功能:
一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序, 首先在编译时, 我们必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如:
gcc -g hello.c -o hello
如果没有-g, 你将看不见程序的函数名、变量名, 所代替的全是运行时的内存地址。当你用-g把调试信息加入之后, 并成功编译目标代码以后, 让我们来看看如何用gdb来调试他。
program 也就是你的执行文件, 一般在当前目录下。
GDB 可以打印出所调试程序的源代码, 当然, 在程序编译时一定要加上-g的参数, 把源程序信息编译到执行文件中。不然就看不到源程序了。当程序停下来以后, GDB会报告程序停在了那个文件的第几行上。你可以用list命令来打印程序的源代码, 默认打印10行, list命令的用法如下所示:
一般是打印当前行的上5行和下5行, 如果显示函数是是上2行下8行, 默认是10行, 当然, 你也可以定制显示的范围, 使用下面命令可以设置一次显示源程序的行数。
2.5 设置断点
简单断点—当前文件
多文件设置断点---其他文件
查询所有断点
条件断点
一般来说, 为断点设置一个条件, 我们使用if关键词, 后面跟其断点条件。设置一个条件断点:
b test.c:8 if intValue == 5
维护断点
如果什么都不指定, 表示disable所有的停止点。
如果什么都不指定, 表示enable所有的停止点。
2.6 调试代码
如果出不去, 看一下函数体中的循环中是否有断点,如果有删掉,或者设置无效
2.7 查看变量的值
查看运行时变量的值
print 打印变量、字符串、表达式等的值, 可简写为p
p count -----打印count的值
自动显示变量的值
你可以设置一些自动显示的变量, 当程序停住时, 或是在你单步跟踪时, 这些变量会自动显示。相关的GDB命令是display。
查看修改变量的值
type = double
$4 = 13
你可以使用set var命令来告诉GDB, width不是你GDB的参数, 而是程序的变量名, 如:
set var width=47 // 将变量var值设置为47
在你改变程序变量取值时, 最好都使用set var格式的GDB命令。
从本章开始学习各种Linux系统函数, 这些函数的用法必须结合Linux内核的工作原理来理解, 因为系统函数正是内核提供给应用程序的接口, 而要理解内核的工作原理, 必须熟练掌握C语言, 因为内核也是用C语言写的, 我们在描述内核工作原理时必然要用“指针”、“结构体”、“链表”这些名词来组织语言, 就像只有掌握了英语才能看懂英文书一样, 只有学好了C语言才能看懂我描述的内核工作原理。
c语言操作文件相关问题:
使用fopen函数打开一个文件, 返回一个FILE* fp, 这个指针指向的结构体有三个重要的成员.
备注:
义中有一个_fileno成员, 这个就是文件描述符
系统调用: 由操作系统实现并提供给外部应用程序的编程接口,
(Application Programming Interface, API), 是应用程序同系统之间数据交互的桥梁.
进程的虚拟地址空间分为用户区和内核区, 其中内核区是受保护的, 用户是不能够对其进行读写操作的;内核区中很重要的一个就是进程管理, 进程管理中有一个区域就是PCB(本质是一个结构体);PCB中有文件描述符表, 文件描述符表中存放着打开的文件描述符, 涉及到文件的IO操作都会用到这个文件描述符.
备注:
pcb:结构体:task_stuct, 该结构体在:
/usr/src/linux-headers-4.4.0-97/include/linux/sched.h:1390
一个进程有一个文件描述符表:1024
虚拟地址空间->内核区->PCB->文件描述表->文件描述符->文件IO操作使用文件描述符