张伟伟,男,西安工程大学电子信息学院,2019级硕士研究生,张宏伟人工智能课题组。
微信公众号:可随时查阅,搜索—张二牛的笔记,内容会分类上传。
研究方向:机器视觉与人工智能。
电子邮件:[email protected]
电子邮件:[email protected]
熟练使用规则编写简单的makefile文件
熟练使用makefile中的变量
熟练使用makefile中的函数
熟练掌握gdb相关调试指令的使用
了解概念:pcb和文件描述符,虚拟地址空间
熟练掌握Linux系统IO函数的使用
makefile文件的命名:makefile 或者 Makefille
makefile文件时用来管理项目工程文件,
makefile由一组规则组成,规则如下:
目标: 依赖
(tab)命令
makefile基本规则三要素:
第一个版本:
makefile文件编写后:
main: mian.c fun1.c fun2.c sum.c
gcc -o main main.c fun1.c fun2.c sum.c
使用make指令直接调用makefile运行
make
若想生成目标, 检查规则中的所有的依赖文件是否都存在:
------如果有的依赖文件不存在, 则向下搜索规则, 看是否有生成该依赖文件的规则:
--------------如果有规则用来生成该依赖文件, 则执行规则中的命令生成依赖文件;
--------------如果没有规则用来生成该依赖文件, 则报错.
------如果所有依赖都存在, 检查规则中的目标是否需要更新, 必须先检查它的所有依赖,依赖中有任何一个被更新, 则目标必须更新.(检查的规则是哪个时间大哪个最新)
总结:
普通变量(自定义字符串)
变量相当于内容替换
变量定义直接用 =
使用变量值用 $(变量名)
下面是变量的定义和使用
foo = abc // 定义变量并赋值
bar = $(foo) // 使用变量, $(变量名)
自带变量(一些系统自带的变量名)
CC = gcc #arm-linux-gcc
CPPFLAGS : C预处理的选项 -I
CFLAGS: C编译器的选项 -Wall -g -c
LDFLAGS : 链接器选项 -L -l
自动变量(只能在规则中的命令使用))
$@: 表示规则中的目标
$<: 表示规则中的第一个条件
$^: 表示规则中的所有条件, 组成一个列表, 以空格隔开, 如果这个列表中有重复的项则消除重复项。
至少在规则的目标定义中要包含’%’, ‘%’表示一个或多个, 在依赖条件中同样可以使用’%’, 依赖条件中的’%’的取值取决于其目标:
%.o:%.c 前后的百分号必须是相同的字符串
makefile-v2
main:main.o fun1.o fun2.o sum.o
gcc -o main main.o fun1.o fun2.o sum.o
main.o:main.c
gcc -o main.o -c main.c -I./
fun1.o:fun1.c
gcc -o fun1.o -c fun1.c
fun2.o:fun2.c
gcc -o fun2.o -c fun2.c
sum.o:sum.c
gcc -o sum.o -c sum.c
makefile-v3
target=main
object=main.o fun1.o fun2.o sum.o
CC=gcc
CPPFLAGS=-I./
$(target):$(object)
$(CC) -o $@ $^
%.o:%.c
$(CC) -o $@ -c $< $(CPPFLAGS)
#main.o:main.c
# gcc -o main.o -c main.c -I./
#
#fun1.o:fun1.c
# gcc -o fun1.o -c fun1.c
#
#fun2.o:fun2.c
# gcc -o fun2.o -c fun2.c
#
#sum.o:sum.c
# gcc -o sum.o -c sum.c
makefile中的函数有很多,在这里给大家介绍两个常用的
匹配替换函数
1. wildcard-查找指定目录下指定类型的文件
src=$(wildcard *.c) //找到当前目录下所有后缀为.c的文件,赋值给src
2. patsubst – 匹配替换
obj=$(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o
---------例如------------
当前目录下有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
target=main
src=$(wildcard ./*.c)
object=$(patsubst %.c,%.o, $(src))
CC=gcc
CPPFLAGS=-I./
$(target):$(object)
$(CC) -o $@ $^
%.o:%.c
$(CC) -o $@ -c $< $(CPPFALGS)
缺点: 每次重新编译都需要手工清理中间.o文件和最终目标文件
第一个出现的目标就是终极目标
.PHONY:clean
clean:
rm -f $(objects) $(target)
src=$(wildcard ./*.c)
object=$(patsubst %.c, %.o, $(src))
target=main
CC=gcc
CPPFLAGS=-I./
$(target):$(object)
$(CC) -o $@ $^
%.o:%.c
$(CC) -o $@ -c $< $(CPPFLAGS)
.PHONY:clean
clean:
-rm -f $(target) $(object)
@不显示执行过程 加-不影响后续程序运行
GDB(GNU Debugger)是GCC的调试工具。
功能:
启动程序, 可以按照你的自定义的要求随心所欲的运行程序。
可让被调试的程序在你所指定的断点处停住。(断点可以是条件表达式)
当程序被停住时, 可以检查此时你的程序中所发生的事。
动态的改变你程序的执行环境。
gdb是在程序运行的结果与预期不符合的时候,可以使用gdb进行调试,
特别注意的是:使用gdb调试需要在编译的时候加上-g参数。
gcc -g -c hello.c
gcc -o hello hello.o
gdb 可执行程序
设置程序运行参数
set args 可指定运行时参数。(如:set args 10 20 30 40 50 )
show args 命令可以查看设置好的运行参数
启动程序
run:程序开始执行, 如果有断点, 停在第一个断点处
start:程序向下执行一行。(在第一条语句处停止)
list:默认显示10行,回车继续显示
如 list 1 list main 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的设置
break #打断点
info #查看断点
b 10 #在第十行打断点
i b #查看断点
next #下一步运行
disable 1 disable 2-4 #使断点失效
enable 1 enable 2-4 enable 1 3 使断点有效
delete 1 #删除断点
b test.c:8 if intValue==5 当值变量intValue断点的值为5时断点生效
维护断点
run 运行程序
next 单步跟踪
step 单步跟踪 (可以进函数内部)
finish 退出进入的函数
until 挑出函数体
quit 退出gdb调试
continue 继续运行程序,可简写为c(若有断点,则跳转到下一个断点)
print &i
p &i
p i
display 变量名 #自动显示变量的值
disable display 1 2
我们要使用IO操作函数,则需要先了解内核的工作原理,他对于底层做了一定的封装。
备注:
头文件stdio.h 的第48行处: typedef struct _IO_FILE FILE;
头文件libio.h 的第241行处: struct _IO_FILE, 这个接头体定义中有一个_fileno成员, 这个就是文件描述符
进程的虚拟地址空间分为用户区和内核区, 其中内核区是受保护的, 用户是不能够对其进行读写操作的;
内核区中很重要的一个就是进程管理, 进程管理中有一个区域就是PCB(本质是一个结构体);
PCB中有文件描述符表, 文件描述符表中存放着打开的文件描述符, 涉及到文件的IO操作都会用到这个文件描述符.
pcb就是一个结构体
虚拟地址空间-》内核区-》PCB-》文件描述表-》文件描述符-》文件IO操作使用文件描述符
函数描述: 打开或者新建一个文件
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数参数:
pathname参数是要打开或创建的文件名,和fopen一样, pathname既可以是相对路径也可以是绝对路径。
flags参数有一系列常数值可供选择, 可以同时选择多个常数用按位或运算符连接起来, 所以这些常数的宏定义都以O_开头,表示or。
必选项:以下三个常数中必须指定一个, 且仅允许指定一个。
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 可读可写打开
以下可选项可以同时指定0个或多个, 和必选项按位或起来作为flags参数。可选项有很多, 这里只介绍几个常用选项:
O_APPEND 表示追加。如果文件已有内容, 这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode, 表示该文件的访问权限。
文件最终权限:mode & ~umask
O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
O_TRUNC 如果文件已存在, 将其长度截断为为0字节。
O_NONBLOCK 对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O(NonblockI/O),非阻塞I/O。
函数返回值:
成功: 返回一个最小且未被占用的文件描述符
失败: 返回-1, 并设置errno值.
close函数
函数描述: 关闭文件
函数原型: int close(int fd);
函数参数: fd文件描述符
函数返回值:
成功返回0
失败返回-1, 并设置errno值.
函数描述: 从打开的设备或文件中读取数据
函数原型: ssize_t read(int fd, void *buf, size_t count);
函数参数:
fd: 文件描述符
buf: 读上来的数据保存在缓冲区buf中
count: buf缓冲区存放的最大字节数
函数返回值:
>0:读取到的字节数
=0:文件读取完毕
-1: 出错,并设置errno
函数描述: 向打开的设备或文件中写数据
函数原型: ssize_t write(int fd, const void *buf, size_t count);
函数参数:
fd:文件描述符
buf:缓冲区,要写入文件或设备的数据
count:buf中数据的长度
函数返回值:
成功:返回写入的字节数
错误:返回-1并设置errno
所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为cfo
#include
#include
off_t lseek(int fd, off_t offset, int whence);
函数描述: 移动文件指针
函数原型: off_t lseek(int fd, off_t offset, int whence);
函数参数:
fd:文件描述符
参数 offset 的含义取决于参数 whence:
如果 whence 是 SEEK_SET,文件偏移量将设置为 offset。
如果 whence 是 SEEK_CUR,文件偏移量将被设置为 cfo 加上 offset,offset 可以为正也可以为负。
如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset 可以为正也可以为负。
函数返回值: 若lseek成功执行, 则返回新的偏移量。
fseek使用的一些例子
lseek函数常用操作
文件指针移动到头部
lseek(fd, 0, SEEK_SET);
获取文件指针当前位置
int len = lseek(fd, 0, SEEK_CUR);
获取文件长度
int len = lseek(fd, 0, SEEK_END);
lseek实现文件拓展
off_t currpos;
// 从文件尾部开始向后拓展1000个字节
currpos = lseek(fd, 1000, SEEK_END);
// 额外执行一次写操作,否则文件无法完成拓展
write(fd, “a”, 1); // 数据随便写
errno是一个全局变量, 当系统调用后若出错会将errno进行设置, perror可以将errno对应的描述信息打印出来.
如:perror(“open”); 如果报错的话打印: open:(空格)错误信息。
函数描述: 获取文件属性
函数原型: int stat(const char *pathname, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
函数返回值:
成功返回 0
失败返回 -1
struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
blksize_t st_blksize; //块大小(文件系统的I/O 缓冲区大小)
blkcnt_t st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
简单示例:
struct stat st;
int Just = stat(args[1], &st);
printf("[%d,%d,%d,%d,%d]",st.st_uid, st.st_gid, st.st_size,st.st_mtime);
互斥用if else与有可能用if,判断文件权限使用&的方法,与位去比。
//stat函数测试: 获取文件类型和权限
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
//int stat(const char *pathname, struct stat *buf);
//获取文件属性
struct stat sb;
stat(argv[1], &sb);
//获取文件类型
if ((sb.st_mode & S_IFMT) == S_IFREG)
{
printf("普通文件\n");
}
else if((sb.st_mode & S_IFMT) ==S_IFDIR)
{
printf("目录文件\n");
}
else if((sb.st_mode & S_IFMT) ==S_IFLNK)
{
printf("连接文件\n");
}
if (S_ISREG(sb.st_mode))
{
printf("普通文件\n");
}
else if(S_ISDIR(sb.st_mode))
{
printf("目录文件\n");
}
else if(S_ISLNK(sb.st_mode))
{
printf("连接文件\n");
}
//判断文件权限
if(sb.st_mode & S_IROTH)
{
printf("---R----");
}
if(sb.st_mode & S_IWOTH)
{
printf("---W----");
}
if(sb.st_mode & S_IXOTH)
{
printf("---X----");
}
printf("\n");
return 0;
}
stat函数和lstat函数的区别
对于普通文件, 这两个函数没有区别, 是一样的.
对于连接文件,调用lstat函数获取的是链接文件本身的属性信息;
而stat函数获取的是链接文件指向的文件的属性信息
opendir函数
函数描述:打开一个目录
函数原型: DIR *opendir(const char *name);
函数返回值: 指向目录的指针
函数参数: 要遍历的目录(相对路径或者绝对路径)
readdir函数
函数描述: 读取目录内容--目录项
函数原型: struct dirent *readdir(DIR *dirp);
函数返回值: 读取的目录项指针
函数参数: opendir函数的返回值
struct dirent
{
ino_t d_ino; // 此目录进入点的inode
off_t d_off; // 目录文件开头至此目录进入点的位移
signed short int d_reclen; // d_name 的长度, 不包含NULL 字符
unsigned char d_type; // d_name 所指的文件类型
char d_name[256]; // 文件名
};
d_type的取值:
DT_BLK - 块设备
DT_CHR - 字符设备
DT_DIR - 目录
DT_LNK - 软连接
DT_FIFO - 管道
DT_REG - 普通文件
DT_SOCK - 套接字
DT_UNKNOWN - 未知
closedir函数
函数描述: 关闭目录
函数原型: int closedir(DIR *dirp);
函数返回值: 成功返回0, 失败返回-1
函数参数: opendir函数的返回值
1.打开目录 opendir
2.循环读目录 readdir
3.关闭目录 closedir
//目录操作测试:opendit readdir closedir
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
//打开目录
//DIR *opendir(const char *name);
DIR *pDir = opendir(argv[1]);
if(pDir==NULL)
{
perror("opendir error");
return -1;
}
//循环读取目录项
//struct dirent *readdir(DIR *dirp);
struct dirent *pDent = NULL;
while((pDent = readdir(pDir))!=NULL)
{
//过滤掉. 和 ..
if(strcmp(pDent->d_name,".")==0 || strcmp(pDent->d_name,"..")==0)
{
continue;
}
printf("[%s]--->",pDent->d_name);
switch(pDent->d_type)
{
case DT_REG:
printf("普通文件\n");
break;
case DT_DIR:
printf("目录文件\n");
break;
case DT_LNK:
printf("链接文件\n");
break;
default:
printf("未知文件\n");
}
}
//关闭目录
closedir(pDir);
}
//dup函数
函数描述: 复制文件描述符
函数原型: int dup(int oldfd);
函数参数: oldfd -要复制的文件描述符
函数返回值:
成功: 返回最小且没被占用的文件描述符
失败: 返回-1, 设置errno值
//dup2函数
函数描述: 复制文件描述符
函数原型: int dup2(int oldfd, int newfd);
newfd 随着 oldfd 变。
函数参数:
oldfd-原来的文件描述符
newfd-复制成的新的文件描述符
函数返回值:
成功: 将oldfd复制给newfd, 两个文件描述符指向同一个文件
失败: 返回-1, 设置errno值
假设newfd已经指向了一个文件,首先close原来打开的文件,然后newfd指向oldfd指向的文件.
若newfd没有被占用,newfd指向oldfd指向的文件
使用实例:
//判断是否指向同一文件,使用fd去写入,用newfd去读
int newfd = dup(fd);
//使用fd对文件进行写操作
write(fd, "hello world", strlen("hello world"));
//使用newfd读文件
char buf[64];
memset(buf, 0x00, sizeof(buf));
int n = read(newfd, buf, sizeof(buf));
printf("read over: n==[%d], buf==[%s]\n", n, buf);
视频16 17 18未看
理解文件重定向的原理:
# 4.6 fcntl函数讲解(改变文件flags标识)
函数描述: 改变已经打开的文件的属性
函数原型: int fcntl(int fd, int cmd, ... /* arg */ );
若cmd为F_DUPFD, 复制文件描述符, 与dup相同
若cmd为F_GETFL, 获取文件描述符的flag属性值
若cmd为 F_SETFL, 设置文件描述符的flag属性
函数返回值:返回值取决于cmd
成功
若cmd为F_DUPFD, 返回一个新的文件描述符
若cmd为F_GETFL, 返回文件描述符的flags值
若cmd为 F_SETFL, 返回0
失败返回-1, 并设置errno值.
fcntl函数常用的操作:
1 复制一个新的文件描述符:
int newfd = fcntl(fd, F_DUPFD, 0);
2 获取文件的属性标志
int flag = fcntl(fd, F_GETFL, 0)
3 设置文件状态标志
flag = flag | O_APPEND;
fcntl(fd, F_SETFL, flag)
4 常用的属性标志
O_APPEND-----设置文件打开为末尾添加
O_NONBLOCK-----设置打开的文件描述符为非阻塞
//获得和设置fd的flags属性
int flags = fcntl(fd, F_GETFL, 0);
flags = flags | O_APPEND;
fcntl(fd, F_SETFL, flags);