Linux系统编程
- 第一章 Linux系统编程入门(下)
- 1.6 GDB 调试
- 1.7 标准C库IO函数和Linux系统IO函数对比
第一章 Linux系统编程入门(上)
第一章 Linux系统编程入门(下)
1.6 GDB 调试
(1)什么是 GDB
-
GDB (
GNU debugger
) 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境, GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。 -
一般来说, GDB 主要帮助你完成下面四个方面的功能:
- 启动程序,可以按照自定义的要求随心所欲的运行程序
- 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的事
- 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG
-
通常,在为调试而编译时,我们会()关掉编译器的优化选项(
-O
) 并打开调试选项(-g
)。另外-Wall
在尽量不影响程序行为的情况下选项打开所有 warning ,也可以发现许多问题,避免一些不必要的 BUG 。gcc -g -Wall program.c -o program
-g
选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb
能找到源文件。
例如,有一个test.c
文件:
// test.c
#include <stdio.h>
#include <stdlib.h>
int test(int a);
int main(int argc, char* argv[]) {
int a, b;
printf("argc = %d\n", argc);
if(argc < 3) {
a = 10;
b = 30;
} else {
a = atoi(argv[1]);
b = atoi(argv[2]);
}
printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", a + b);
for(int i = 0; i < a; ++i) {
printf("i = %d\n", i);
// 函数调用
int res = test(i);
printf("res value: %d\n", res);
}
printf("THE END !!!\n");
return 0;
}
int test(int a) {
int num = 0;
for(int i = 0; i < a; ++i) {
num += i;
}
return num;
}
gcc test.c -o test -g
(2)GDB命令——启动、退出、查看代码
-
启动和退出
gdb 可执行程序 # 进入gdb quit 或 q # 在调试命令行输入quit就可退出调试
-
给程序设置参数 / 获取设置参数
set args 10 20 show args
-
GDB 使用帮助
help
-
查看当前文件代码
# 也可以在终端使用 vim 查看 list/l #(从默认位置显示) list/l 行号 #(从指定行号显示,以该行号为中心,显示指定的行的上下文) list/l 函数名 #(从指定的函数显示, 也是上下文)
-
查看非当前文件代码
list/l 文件名:行号 list/l 文件名:函数名
-
一个程序有好几个文件组成,当我们调试主函数所在程序时,其他文件的代码也需要查看,就可以使用上述命令。一个项目中含:
bubble.cpp
,select.cpp
,main.cpp
。 -
在调试时,默认显示
main.cpp
中的代码。如下图所示: -
查看非当前文件代码 (默认显示10行)
-
-
设置显示的行数
show list/listsize set list/listsize 行数
(3)GDB命令——断点操作
-
设置断点
b/break 行号 b/break 函数名 b/break 文件名:行号 b/break 文件名:函数
-
查看断点
i/info b/break
num
: 断点编号Type
下的breakpoint
说明该点为断点Disp
为断点状态Enb
是yes
代表为有效断点adress
为断点地址What
说明断点的在那个文件的第几行。
-
删除断点
d/del/delete 断点编号
-
设置断点无效
dis/disable 断点编号
-
设置断点生效
ena/enable 断点编号
-
设置条件断点(一般用在循环的位置)
b/break 10 if i==5
(4)GDB命令——调试命令
-
运行 GDB 程序
start # (程序停在第一行) run # (遇到断点才停)
-
继续运行,到下一个断点停
c/continue
-
向下执行一行代码(不会进入函数体)
n/next
-
变量操作
p/print 变量名 # (打印变量值) ptype 变量名 # (打印变量类型)
-
向下单步调试(遇到函数进入函数体)
s/step finish # (跳出函数体)
-
自动变量操作
display 变量名 # (自动打印指定变量的值,每次运行到该变量,都会自动打印) i/info display undisplay 编号
-
其它操作
set var 变量名=变量值 # (循环中用的较多) until #(跳出循环 (里面不能有断点,不然又会运行到断点处))
1.7 标准C库IO函数和Linux系统IO函数对比
- 文件IO:从 内存 的角度看,输入
Input
(文件 -> 内存),输出Output
(内存 -> 文件)
(1)标准 C 库 IO 和 Linux 系统 IO 的关系
-
标准 C 库 IO 函数
- 标准 C 库 IO 函数,可跨平台。
- 标准 C 库 IO 函数,高于 Linux系统IO函数 (标准C 调用 Linux系统的IO函数),尽量使用标准 C 库 IO 函数,缓冲区可以提高效率。
- 而网络通信中应该使用Linux系统的IO函数。
- 标准 C 库 IO 和 Linux 系统 IO 的关系
(2)虚拟地址空间
(3)文件描述符
- 文件描述符 在 内核区。
- 文件描述符表是一个数组(默认大小为1024),可以存储
n
个文件描述符。 - 每个进程都有个文件描述符表,所以最大能打开文件的个数为
1024
个。 - Linux 系统当中不管什么 硬件设备(显示器、网卡、显卡)都一个 虚拟 为一个文件。(Linux中一切皆文件!)
(4)Linux 系统 IO 函数
- Linux 系统中的
函数API
在2
中查找,而标准C库中的API
说明文档应该在3
中查找。
# 在 Linux终端 输入以下命令来查看手册
man 2 open
// 打开一个已经存在的文件
int open(const char *pathname, int flags);
// 创建一个新文件
int open(const char *pathname, int flags, mode_t mode); // 可变参数
// 关闭文件
int close(int fd); // 传入参数:文件描述符
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
-
int open(const char *pathname, int flags);
打开一个已经存在的文件-
参数:
-
pathname
:要打开的文件路径 -
flags
:对文件的操作权限设置还有其他的设置O_RDONLY
,O_WRONLY
,O_RDWR
这三个设置是互斥的
-
-
返回值:返回一个新的文件描述符,如果调用失败,返回
-1
errno
:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。#include <stdio.h>
标准C库void perror(const char *s);
作用:打印errno
对应的错误描述s
参数:用户描述,比如hello
,最终输出的内容是hello:xxx
(实际的错误描述)
-
-
int open(const char *pathname, int flags, mode_t mode);
-
参数:
-
pathname
:要创建的文件的路径 -
flags
:对文件的操作权限和其他的设置
- 必选项:O_RDONLY
,O_WRONLY
,O_RDWR
这三个之间是互斥的
- 可选项:O_CREAT
文件不存在,创建新文件 -
mode
:八进制的数,表示创建出的新的文件的操作权限,比如:0775
最终的权限是:
mode & ~umask
0777 -> 111111111
& 0775 -> 111111101
------------------------------------------
111111101- 按位与:0和任何数都为0
umask
的作用就是 抹去某些权限。
flags
参数是一个int
类型的数据,占4个字节,32位。
flags
32个位,每一位就是一个标志位。(按位或) -
-
例如:
create.c
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> // Unix 的标准IO #include <stdio.h> // 标准C库 int main() { // 创建一个新的文件 int fd = open("create.txt", O_RDWR | O_CREAT, 0777); if(fd == -1) { perror("open"); } // 关闭 close(fd); return 0; }
-
-
ssize_t read(int fd, void *buf, size_t count);
-
参数:
fd
:文件描述符,open
得到的,通过这个文件描述符操作某个文件buf
:读取数据存放的地方,数组的地址(传出参数)count
:指定的 数组的大小 (大于数组大小则截断)
-
返回值:
- 成功: `>0`: 返回实际的读取到的 **字节数** `=0`:文件已经读取完了 - 失败:`-1` ,并且设置 `errno`
-
-
ssize_t write(int fd, const void *buf, size_t count);
-
参数:
-
fd
:文件描述符,open
得到的,通过这个文件描述符操作某个文件 -
buf
:要往磁盘写入的数据,数组 -
count
:要写的数据的实际的大小
-
-
返回值:
-
成功:实际写入的字节数
-
失败:返回
-1
,并设置errno
-
#include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> // 存放 宏 #include <fcntl.h> int main() { // 1.通过open打开english.txt文件 int srcfd = open("english.txt", O_RDONLY); if(srcfd == -1) { perror("open"); return -1; } // 2.创建一个新的文件(拷贝文件) int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664); if(destfd == -1) { perror("open"); return -1; } // 3.频繁的读写操作 char buf[1024] = {0}; int len = 0; while((len = read(srcfd, buf, sizeof(buf))) > 0) { write(destfd, buf, len); } // 4.关闭文件 close(destfd); close(srcfd); return 0; }
-
(5)stat
结构体
struct stat {
dev_t st_dev; // 文件的设备编号
ino_t st_ino; // 节点
mode_t st_mode; // 文件的类型和存取权限
nlink_t st_nlink; // 连到该文件的硬接数目
uid_t st_uid; // 用户 ID
gid_t st_gid; // 组ID
dev_t st_rdev; // 设备文件的编号
off_t st_size; // 文件字节数 (文件大小 )
blksize_t st_blksize; // 块大小
blkcnt_t st_blocks; // 块数
time_t st_atime; // 最后一次访问时间
time_t st_mtime; // 最后一次修改时间
time_t st_ctime; // 最后一次改变时间 (指属性 )
};
(6)st_mode
变量
(7)文件属性操作函数
// 判断某个文件是否有某个权限,或者判断文件是否存在
int access(const char *pathname, int mode);
// 修改文件的权限
int chmod(const char *filename, int mode);
// 修改文件的所有者和所在组
int chown(const char *path, uid_t owner, gid_t group);
// 缩减或者扩展文件的尺寸至指定的大小
int truncate(const char *path, off_t length);
-
int access(const char *pathname, int mode);
-
作用:判断某个文件是否有某个权限,或者判断文件是否存在
-
参数:
-
pathname
: 判断的文件路径 -
mode
:R_OK
: 判断是否有读权限W_OK
: 判断是否有写权限X_OK
: 判断是否有执行权限F_OK
: 判断文件是否存在
-
-
返回值:成功返回
0
, 失败返回-1
-
-
int chmod(const char *pathname, mode_t mode);
- 作用:修改文件的权限
- 参数:
pathname
: 需要修改的文件的路径mode
: 需要修改的权限值,八进制的数
- 返回值:成功返回
0
,失败返回-1
-
int truncate(const char *path, off_t length);
- 作用:缩减或者扩展文件的尺寸至指定的大小
- 参数:
path
: 需要修改的文件的路径length
: 需要最终文件变成的大小
- 返回值:
- 成功返回0, 失败返回-1
(8)目录操作函数
// 修改目录名字
int rename(const char *oldpath, const char *newpath);
// 修改进程的工作目录
int chdir(const char *path);
// 获取当前工作目录
char *getcwd(char *buf, size_t size);
// 创建一个目录
int mkdir(const char *pathname, mode_t mode);
// 删除空目录
int rmdir(const char *pathname);
查看 系统函数 的帮助文档:
man 2 mkdir
查看 Linux命令 的帮助文档:
man mkdir
-
int chdir(const char *path);
- 作用:修改进程的工作目录
- 比如,在
/home/nowcoder
启动了一个可执行程序a.out
, 进程的工作目录/home/nowcoder
- 比如,在
- 参数:
path
: 需要修改的工作目录
- 作用:修改进程的工作目录
-
char *getcwd(char *buf, size_t size);
-
作用:获取当前工作目录
-
参数:
buf
: 存储的路径,指向的是一个数组(传出参数)size
: 数组的大小
-
返回值:
- 返回的指向的一块内存,这个数据就是第一个参数
例如:
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main() {
// 获取当前的工作目录
char buf[128];
getcwd(buf, sizeof(buf));
printf("当前的工作目录是:%s\n", buf);
// 修改工作目录
int ret = chdir("/home/nowcoder/Linux/lesson13");
if(ret == -1) {
perror("chdir");
return -1;
}
// 创建一个新的文件
int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664);
if(fd == -1) {
perror("open");
return -1;
}
close(fd);
// 获取当前的工作目录
char buf1[128];
getcwd(buf1, sizeof(buf1));
printf("当前的工作目录是:%s\n", buf1);
return 0;
}
输出:
当前的工作目录是:
/home/nowcoder/Linux/lesson14
当前的工作目录是:/home/nowcoder/Linux/lesson13
int mkdir(const char *pathname, mode_t mode);
- 作用:创建一个目录
- 参数:
pathname
: 创建的目录的路径mode
: 权限,八进制的数
- 返回值:
- 成功返回
0
, 失败返回-1
- 成功返回
(9)目录遍历函数
// 打开一个目录
DIR *opendir(const char *name);
// 读取目录中的数据(while循环遍历)
struct dirent *readdir(DIR *dirp);
// 关闭目录
int closedir(DIR *dirp);
这3个函数在第3章 标准c库 中:
man 3 opendir
在Linux中,
man
命令用于查看手册页(manual pages
)以获取关于系统命令、库函数等的信息。手册页被分为多个章节,其中常见的有man 1
、man 2
、man 3
等。这些数字代表手册页的不同章节,每个章节有特定的主题。
man 1
:用于 可执行程序 的手册页。包括系统命令和可执行程序的文档。man 2
:用于 系统调用 的手册页。包括内核系统调用的详细文档。man 3
:用于 库函数 的手册页。包括C标准库中提供的函数的详细文档。
-
DIR *opendir(const char *name);
- 参数:
name
: 需要打开的 目录的名称
- 返回值:
DIR*
类型,理解为目录流- 错误返回
NULL
- 参数:
-
struct dirent *readdir(DIR *dirp);
- 参数:
dirp
是opendir
返回的结果 - 返回值:
struct dirent
,代表读取到的文件的信息- 读取到了末尾或者失败了,返回
NULL
- 参数:
dirent
结构体和 d_type
struct dirent
{
// 此目录进入点的 inode
ino_t d_ino;
// 目录文件开头至此目录进入点的位移
off_t d_off;
// d_name 的长度 , 不包含 NULL 字符
unsigned short int d_reclen;
// d_name 所指的文件类型
unsigned char d_type;
// 文件名
char d_name[256];
};
d_type //
DT_BLK -块设备
DT_CHR -字符设备
DT_DIR -目录
DT_LNK -软连接
DT_FIFO -管道
DT_REG -普通文件
DT_SOCK -套接字
DT_UNKNOWN -未知
(10)dup
、 dup2
函数
int dup(int oldfd); // 复制文件描述符
int dup2(int oldfd, int newfd); //重定向文件描述符
-
int dup(int oldfd);
- 作用:复制 一个新的文件描述符
fd=3, int fd1 = dup(fd),
fd
指向的是a.txt
,fd1
也是指向a.txt
从空闲的文件描述符表中找一个 最小的,作为新的拷贝的文件描述符
- 作用:复制 一个新的文件描述符
-
int dup2(int oldfd, int newfd);
- 作用:重定向 文件描述符
oldfd
指向a.txt
,newfd
指向b.txt
- 调用函数成功后:
newfd
和b.txt
做close
,newfd
指向了a.txt
,返回newfd
oldfd
必须是一个有效的文件描述符- 如果
oldfd
和newfd
值相同,相当于什么都没有做
- 作用:重定向 文件描述符
(11)fcntl
函数
int fcntl(int fd, int cmd, ... /* arg */ );
// 复制文件描述符
// 设置/获取文件的状态标志
-
int fcntl(int fd, int cmd, ...);
-
参数:
-
fd
: 表示需要操作的 文件描述符 -
cmd
: 表示对文件描述符进行如何操作-
F_DUPFD
: 复制文件描述符, 复制的是第一个参数fd
,得到一个新的文件描述符(返回值)int ret = fcntl(fd, F_DUPFD);
-
F_GETFL
: 获取指定的文件描述符 文件状态`flag获取的
flag
和我们通过open
函数传递的flag
是一个东西。 -
F_SETFL
: 设置 文件描述符 文件状态flag
必选项:
O_RDONLY
,O_WRONLY
,O_RDWR
不可以被修改可选性:
O_APPEND
,O_NONBLOCK
O_APPEND
表示 追加数据
O_NONBLOK
设置成 非阻塞
-
-
阻塞和非阻塞:描述的是函数调用的行为。
-
具体查看:
man 2 fcntl
#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <string.h> int main() { // 1.复制文件描述符 // int fd = open("1.txt", O_RDONLY); // int ret = fcntl(fd, F_DUPFD); // 2.修改或者获取文件状态flag int fd = open("1.txt", O_RDWR); if(fd == -1) { perror("open"); return -1; } // 获取文件描述符状态flag int flag = fcntl(fd, F_GETFL); if(flag == -1) { perror("fcntl"); return -1; } flag |= O_APPEND; // flag = flag | O_APPEND // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记 int ret = fcntl(fd, F_SETFL, flag); if(ret == -1) { perror("fcntl"); return -1; } char * str = "nihao"; write(fd, str, strlen(str)); close(fd); return 0; }
-
注:仅供学习参考,如有不足,欢迎指正!