Linux系统编程入门(下)

网络编程系列文章

第1章 Linux系统编程入门(上)
第1章 Linux系统编程入门(下)

第2章 Linux多进程开发(上)
第2章 Linux多进程开发(下)

第3章 Linux多线程开发

第4章 Linux网络编程


第5章 Web服务器

第一章 Linux系统编程入门(下)

1.6 GDB 调试

(1)什么是 GDB

  • GDB (GNU debugger) 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境, GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。

  • 一般来说, GDB 主要帮助你完成下面四个方面的功能

    1. 启动程序,可以按照自定义的要求随心所欲的运行程序
    2. 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式
    3. 当程序被停住时,可以检查此时程序中所发生的事
    4. 可以改变程序,将一个 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.cppselect.cppmain.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断点状态
    • Enbyes 代表为有效断点
    • 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 系统中的 函数API2 中查找,而标准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 1man 2man 3 等。这些数字代表手册页的不同章节,每个章节有特定的主题。

  • man 1:用于 可执行程序 的手册页。包括系统命令和可执行程序的文档。
  • man 2:用于 系统调用 的手册页。包括内核系统调用的详细文档。
  • man 3:用于 库函数 的手册页。包括C标准库中提供的函数的详细文档。
  • DIR *opendir(const char *name);

    • 参数
      • name: 需要打开的 目录的名称
    • 返回值
      • DIR* 类型,理解为目录流
      • 错误返回 NULL
  • struct dirent *readdir(DIR *dirp);

    • 参数dirpopendir 返回的结果
    • 返回值
      • 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)dupdup2 函数

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

    • 调用函数成功后:newfdb.txtclose, newfd 指向了 a.txt ,返回 newfd
    • oldfd 必须是一个有效的文件描述符
    • 如果oldfdnewfd 值相同,相当于什么都没有做

(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;
    }
    

注:仅供学习参考,如有不足,欢迎指正!

  • 19
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷酷的懒虫

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值