Linux 系统编程

linux系统编程

    • [1. 文件权限有哪几种?](#1. 文件权限有哪几种?)
    • [2. cp命令的执行是否成功依赖于哪些权限?](#2. cp命令的执行是否成功依赖于哪些权限?)
    • [3. 目录的读、写和执行权限影响了哪些操作?](#3. 目录的读、写和执行权限影响了哪些操作?)
    • [4. 什么是数字设定法,为什么可以使用数字设定法?](#4. 什么是数字设定法,为什么可以使用数字设定法?)
    • [5. 文件创建掩码有什么影响?如何查看和修改文件创建掩码?](#5. 文件创建掩码有什么影响?如何查看和修改文件创建掩码?)
    • [6 . 描述一下find命令的格式。](#6 . 描述一下find命令的格式。)
    • [7. 常用的通配符有哪些?分别有什么效果?](#7. 常用的通配符有哪些?分别有什么效果?)
    • [8. 在/usr/share/doc找到所有以html结尾且大小大于100个block的文件,并且使用ls -l展示其详细信息。](#8. 在/usr/share/doc找到所有以html结尾且大小大于100个block的文件,并且使用ls -l展示其详细信息。)
    • [9 . 如何检查整体磁盘空间使用状况?如何获知本目录下所有文件的总大小?](#9 . 如何检查整体磁盘空间使用状况?如何获知本目录下所有文件的总大小?)
    • [10. 如果wc命令不加文件参数会有怎么样的效果?(回顾cat命令的效果)行数,词数,字符数](#10. 如果wc命令不加文件参数会有怎么样的效果?(回顾cat命令的效果)行数,词数,字符数)
    • [11. 对一些重复的数据执行排序并去重的操作。](#11. 对一些重复的数据执行排序并去重的操作。)
    • [12. 整理学习过的正则表达式规则。](#12. 整理学习过的正则表达式规则。)
    • [13. vim存在哪种模式?互相之间怎么切换?](#13. vim存在哪种模式?互相之间怎么切换?)
    • [14. 描述一下生成可执行程序的流程?说明各个过程中使用的命令参数和输入输出文件类型。](#14. 描述一下生成可执行程序的流程?说明各个过程中使用的命令参数和输入输出文件类型。)
    • [15. 整理已经学习过的预处理指令。](#15. 整理已经学习过的预处理指令。)
    • [16. 给下列代码生成的汇编语言文件增加注释。](#16. 给下列代码生成的汇编语言文件增加注释。)
    • [17. 说明一下使用动态库和静态库的区别。然后分别生成动态库和静态库。](#17. 说明一下使用动态库和静态库的区别。然后分别生成动态库和静态库。)
    • [18. 整理所有用到的GCC指令](#18. 整理所有用到的GCC指令)
    • [19. gdb命令](#19. gdb命令)
    • [20. 说明一下使用动态库和静态库的区别。](#20. 说明一下使用动态库和静态库的区别。)
    • [21. 动态库链接的顺序有影响吗?下面两种链接方法,假设第一个指令能执行成功,第二个指令一定能成功吗?](#21. 动态库链接的顺序有影响吗?下面两种链接方法,假设第一个指令能执行成功,第二个指令一定能成功吗?)

1. 文件权限有哪几种?

可读可写可执行

2. cp命令的执行是否成功依赖于哪些权限?

在Linux中,cp命令的执行依赖于目标文件或目录的权限,具体来说:

  1. 对于要复制的文件,cp命令的执行者至少要具备读权限r,这是因为复制文件至少要知道文件的内容。
  2. 对于目标文件,cp命令的执行者需要具备写入权限w,这样才能够将源文件的内容写入目标文件。
  3. 对于目标目录,cp命令的执行者需要具备读权限r和执行权限x,这样才能够读取目标目录的内容并且进入目标目录。

综上所述,cp命令的执行依赖于对目标文件或目录的读、写和执行权限。如果权限不足,cp命令将无法执行复制操作。

3. 目录的读、写和执行权限影响了哪些操作?

  • 浏览目录内容
  • 创建和删除文件和目录
  • 修改文件和目录
  • 切换目录
  • 访问子文件

4. 什么是数字设定法,为什么可以使用数字设定法?

  • 通过三位八进制数控制不同用户 一位八进制写成二进制可以分别表示读写执行

  • 因为简洁,灵活,比较高效

5. 文件创建掩码有什么影响?如何查看和修改文件创建掩码?

  • 可以代表了文件的相应权限,
  • 用umask命令

6 . 描述一下find命令的格式。

find指令在Linux中用于查找文件和目录。其基本格式如下:

find [路径] [表达式]

其中,路径指定了查找的起始目录,默认为当前目录。表达式指定了查找的条件。

例如,要在当前目录及其子目录中查找所有名称为example.txt的文件,可以使用以下命令:

find . -name example.txt

这将在当前目录及其子目录中查找所有名称为example.txt的文件。

7. 常用的通配符有哪些?分别有什么效果?

在Linux系统中,常用的通配符有以下几种:

  1. *:代表零个或多个字符。例如,*.txt表示以.txt结尾的所有文件。

  2. ?:代表单个字符。例如,file?.txt表示以file开头,接着是一个字符,然后是.txt结尾的文件。

  3. []:用来匹配指定范围内的字符。例如,[abc]表示匹配字符abc中的任意一个字符。

  4. [!][^]:用来匹配不在指定范围内的字符。例如,[!0-9]表示匹配不是数字的任意字符。

这些通配符可以在命令行中配合lsfind等命令使用,用于匹配文件名或路径名。

8. 在/usr/share/doc找到所有以html结尾且大小大于100个block的文件,并且使用ls -l展示其详细信息。

bash 复制代码
find /usr/share/doc -type f -name "*.html" -size +100c -exec ls -l {} \;

9 . 如何检查整体磁盘空间使用状况?如何获知本目录下所有文件的总大小?

在Linux下,您可以使用以下命令来检查磁盘空间的使用状况和获取目录下所有文件的总大小:

  1. df 命令:用于显示磁盘分区的总空间、已用空间和可用空间。例如,df -h命令将以人类可读的格式显示磁盘空间使用情况。
  2. du 命令:用于估算文件和目录所占用的磁盘空间。例如,du -sh <目录名>命令将显示指定目录的总大小。
  3. ls 命令:用于列出目录中的文件和文件夹。结合-l参数,可以显示文件和文件夹的大小。例如,ls -lh命令将以人类可读的格式显示目录内容。

要获取当前目录下所有文件的总大小,您可以使用以下命令:

bash 复制代码
du -sh * | grep total

这将估算当前目录下所有文件和子目录的大小,并显示总计大小。

10. 如果wc命令不加文件参数会有怎么样的效果?(回顾cat命令的效果)行数,词数,字符数

如果不给wc命令提供文件参数,则wc命令将等待标准输入。你可以通过键盘输入文本,然后按Ctrl+D(在Linux/Unix系统中表示EOF,即文件结束符),wc命令会统计输入的文本的行数、词数和字符数,并在最后输出这些统计信息。与cat命令不带参数的效果类似,但cat命令会将输入的文本直接显示在终端上,而不会进行统计。

11. 对一些重复的数据执行排序并去重的操作。

要对重复的数据执行排序并去重的操作,可以使用sortuniq命令的组合。假设你有一个文件data.txt包含重复的数据,你可以这样操作:

matlab 复制代码
sort data.txt | uniq

这将首先对数据进行排序,然后使用uniq命令去除连续重复的行,最终得到一个排序并去重后的结果。

12. 整理学习过的正则表达式规则。

正则表达式是用于匹配和处理文本的强大工具,它可以用来查找、替换和验证文本数据。以下是一些常用的正则表达式规则:

  1. .:匹配任意单个字符,除了换行符。

  2. *:匹配前面的字符零次或多次。

  3. +:匹配前面的字符一次或多次。

  4. ?:匹配前面的字符零次或一次,表示可选。

  5. ^:匹配字符串的开头。

  6. $:匹配字符串的结尾。

  7. []:字符类,匹配括号内的任意一个字符。

  8. [^]:否定字符类,匹配除括号内字符外的任意一个字符。

  9. ():捕获组,用于捕获匹配的子串。

  10. \d:匹配一个数字,相当于[0-9]

  11. \D:匹配一个非数字,相当于[^0-9]

  12. \w:匹配一个单词字符,包括字母、数字、下划线,相当于[A-Za-z0-9_]

  13. \W:匹配一个非单词字符,相当于[^A-Za-z0-9_]

  14. \s:匹配一个空白字符,包括空格、制表符、换行符等。

  15. \S:匹配一个非空白字符。

这些规则可以通过组合和重复来构建更复杂的表达式,用于实现各种文本处理操作。

13. vim存在哪种模式?互相之间怎么切换?

// 这里写a的代码

在 Vim 中,存在多种模式,主要包括以下几种:

  1. 正常模式(Normal Mode):默认模式,在该模式下可以执行各种命令,如移动光标、复制粘贴等。

  2. 插入模式(Insert Mode):用于输入文本的模式,类似于普通编辑器的模式。

  3. 可视模式(Visual Mode):用于选中文本块,进行复制、剪切等操作。

  4. 命令行模式(Command-Line Mode):用于执行命令,如保存文件、退出编辑器等。

切换不同模式的方式如下:

  • 正常模式 切换到插入模式 :按下i键或a键进入插入模式,在光标前或后插入文本。
  • 插入模式 切换到正常模式 :按下Esc键。
  • 正常模式 切换到可视模式 :按下v键,进入字符级可视模式;或按下V键,进入行级可视模式。
  • 可视模式 切换到正常模式 :再次按下Esc键。
  • 正常模式 切换到命令行模式 :按下:键,然后输入命令。
  • 命令行模式 返回正常模式 :按下Enter键执行命令,返回正常模式。

14. 描述一下生成可执行程序的流程?说明各个过程中使用的命令参数和输入输出文件类型。

生成可执行程序的一般流程如下:

  1. 编写源代码:首先编写程序的源代码文件,通常是C、C++、或其他支持的编程语言。

  2. 编译源代码:使用编译器将源代码文件编译成目标文件。编译器的命令通常是:

    sh 复制代码
    gcc -c source_file.c -o object_file.o

    其中,-c选项表示只编译不链接,source_file.c是源代码文件,object_file.o是生成的目标文件。

  3. 链接目标文件:使用链接器将所有的目标文件和库文件链接成可执行文件。链接器的命令通常是:

    sh 复制代码
    gcc object_file1.o object_file2.o -o executable_file

    其中,object_file1.oobject_file2.o是编译生成的目标文件,executable_file是生成的可执行文件。

  4. 运行可执行文件:最后可以通过命令行或其他方式运行生成的可执行文件:

    sh 复制代码
    ./executable_file

这是一个简单的流程,实际中可能会涉及到更多的步骤和复杂性。例如,对于大型项目,可能会使用Makefile来管理编译和链接过程;对于C++程序,可能需要包含头文件和使用C++编译器等。

15. 整理已经学习过的预处理指令。

预处理器是编译器的一部分,用于在实际编译之前对源代码进行一些处理。预处理器指令是在源代码中使用的特殊指令,以告诉预处理器执行特定的操作。以下是一些常见的预处理器指令:

  1. #include:包含另一个文件的内容。例如:

    c 复制代码
    #include <stdio.h>
  2. #define:定义一个宏。例如:

    c 复制代码
    #define MAX_SIZE 100
  3. #ifdef#ifndef#endif:条件编译指令,用于在编译时根据条件选择性地包含或排除代码块。例如:

    c 复制代码
    #ifdef DEBUG
    printf("Debug mode\n");
    #endif
  4. #if#elif#else:条件编译指令,用于在编译时根据条件选择性地包含或排除代码块。例如:

    c 复制代码
    #if DEBUG_LEVEL > 2
    printf("Debug level is high\n");
    #elif DEBUG_LEVEL == 2
    printf("Debug level is medium\n");
    #else
    printf("Debug level is low\n");
    #endif
  5. #undef:取消已定义的宏。例如:

    c 复制代码
    #undef MAX_SIZE
  6. #pragma:用于向编译器传递特定的指令或控制编译器的行为。例如:

    c 复制代码
    #pragma once

这些预处理器指令可以在源代码中使用,预处理器会在编译实际开始之前处理这些指令,对源代码进行一些修改或处理。

16. 给下列代码生成的汇编语言文件增加注释。

matlab 复制代码
func1(int*, int):
        push    rbp                 ; 保存旧的基址指针
        mov     rbp, rsp            ; 设置新的基址指针
        mov     QWORD PTR [rbp-24], rdi    ; 将第一个参数存储到栈上的位置
        mov     DWORD PTR [rbp-28], esi    ; 将第二个参数存储到栈上的位置
        mov     DWORD PTR [rbp-4], 0      ; 初始化局部变量为0
        mov     DWORD PTR [rbp-8], 0      ; 初始化循环计数器为0
        jmp     .L2                 ; 跳转到循环开始处
.L3:
        mov     eax, DWORD PTR [rbp-8]    ; 将循环计数器加载到寄存器
        cdqe                            ; 将eax扩展为rax(64位寄存器)
        lea     rdx, [0+rax*4]           ; 计算数组偏移量
        mov     rax, QWORD PTR [rbp-24]  ; 加载数组地址到rax
        add     rax, rdx                 ; 计算数组元素地址
        mov     eax, DWORD PTR [rax]     ; 加载数组元素值到eax
        add     DWORD PTR [rbp-4], eax    ; 累加到局部变量
        add     DWORD PTR [rbp-8], 1      ; 计数器加1
.L2:
        mov     eax, DWORD PTR [rbp-8]    ; 加载循环计数器
        cmp     eax, DWORD PTR [rbp-28]   ; 比较循环计数器和参数大小
        jl      .L3                     ; 如果循环计数器小于参数,则继续循环
        mov     eax, DWORD PTR [rbp-4]    ; 将累加结果返回
        pop     rbp                     ; 恢复旧的基址指针
        ret                         ; 返回

func2(int, int):
        push    rbp                 ; 保存旧的基址指针
        mov     rbp, rsp            ; 设置新的基址指针
        mov     DWORD PTR [rbp-20], edi    ; 将第一个参数存储到栈上的位置
        mov     DWORD PTR [rbp-24], esi    ; 将第二个参数存储到栈上的位置
        mov     eax, DWORD PTR [rbp-20]   ; 将第一个参数移动到eax
        mov     DWORD PTR [rbp-4], eax    ; 将eax的值存储到栈上的位置
        mov     eax, DWORD PTR [rbp-24]   ; 将第二个参数移动到eax
        mov     DWORD PTR [rbp-20], eax   ; 将eax的值存储到栈上的位置
        mov     eax, DWORD PTR [rbp-4]    ; 将存储在栈上的第一个参数移动到eax
        mov     DWORD PTR [rbp-24], eax   ; 将eax的值存储到栈上的位置
        nop                         ; 空操作
        pop     rbp                 ; 恢复旧的基址指针
        ret                         ; 返回

func3(int*, int*):
        push    rbp                 ; 保存旧的基址指针
        mov     rbp, rsp            ; 设置新的基址指针
        mov     QWORD PTR [rbp-24], rdi    ; 将第一个参数存储到栈上的位置
        mov     QWORD PTR [rbp-32], rsi    ; 将第二个参数存储到栈上的位置
        mov     rax, QWORD PTR [rbp-24]   ; 将第一个参数(指针)加载到rax
        mov     eax, DWORD PTR [rax]      ; 将指针指向的值加载到eax
        mov     DWORD PTR [rbp-4], eax    ; 将eax的值存储到栈上的位置
        mov     rax, QWORD PTR [rbp-32]   ; 将第二个参数(指针)加载到rax
        mov     edx, DWORD PTR [rax]      ; 将指针指向的值加载到edx
        mov     rax, QWORD PTR [rbp-24]   ; 将第一个参数(指针)加载到rax
        mov     DWORD PTR [rax], edx      ; 将edx的值存储到指针指向的位置
        mov     rax, QWORD PTR [rbp-32]   ; 将第二个参数(指针)加载到rax
        mov     edx, DWORD PTR [rbp-4]     ; 将存储在栈上的第一个参数加载到edx
        mov     DWORD PTR [rax], edx      ; 将edx的值存储到指针指向的位置
       
        nop                         ; 空操作
        pop     rbp                 ; 恢复旧的基址指针
        ret                         ; 返回

main:
        push    rbp                 ; 保存旧的基址指针
        mov     rbp, rsp            ; 设置新的基址指针
        sub     rsp, 48             ; 在栈上分配空间
        mov     DWORD PTR [rbp-12], 1     ; 将1存储到栈上的位置
        mov     DWORD PTR [rbp-4], 3      ; 将3存储到栈上的位置
        mov     DWORD PTR [rbp-16], 5     ; 将5存储到栈上的位置
        mov     DWORD PTR [rbp-48], 1     ; 将1存储到栈上的位置
        mov     DWORD PTR [rbp-44], 2     ; 将2存储到栈上的位置
        mov     DWORD PTR [rbp-40], 3     ; 将3存储到栈上的位置
        mov     DWORD PTR [rbp-36], 4     ; 将4存储到栈上的位置
        mov     DWORD PTR [rbp-32], 5     ; 将5存储到栈上的位置
        mov     DWORD PTR [rbp-28], 6     ; 将6存储到栈上的位置
        lea     rax, [rbp-48]            ; 计算数组地址
        mov     esi, 6                   ; 设置循环次数
        mov     rdi, rax                 ; 设置数组地址参数
        call    func1(int*, int)         ; 调用func1函数
        mov     DWORD PTR [rbp-8], eax    ; 将返回值存储到栈上的位置
        mov     eax, DWORD PTR [rbp-12]   ; 加载1到eax
        mov     edx, DWORD PTR [rbp-4]    ; 加载3到edx
        mov     esi, edx                 ; 将edx的值存储到esi
        mov     edi, eax                 ; 将eax的值存储到edi
        call    func2(int, int)          ; 调用func2函数
        lea     rdx, [rbp-16]            ; 计算指针地址
        lea     rax, [rbp-12]            ; 计算指针地址
        mov     rsi, rdx                 ; 设置指针参数
        mov     rdi, rax                 ; 设置指针参数
        call    func3(int*, int*)        ; 调用func3函数
        mov     eax, 0                   ; 设置返回值为0
        leave                           ; 恢复旧的基址指针并退出
        ret                             ; 返回

17. 说明一下使用动态库和静态库的区别。然后分别生成动态库和静态库。

  • 静态库(Static Library)

    • 静态库在链接时会被完整地复制到可执行文件中,因此可执行文件会变大。
    • 静态库的代码是静态链接的,意味着库的代码在编译时就被链接到可执行文件中,因此可执行文件在运行时不需要外部库的支持。
    • 静态库适用于需要在多个项目中共享且不频繁更新的代码,因为它们的更新需要重新编译整个程序。
  • 动态库(Dynamic Library)

    • 动态库在程序运行时才被加载到内存中,并被多个程序共享,因此可以节省内存。
    • 动态库的代码是动态链接的,意味着库的代码在运行时才被加载到内存中,因此可执行文件需要依赖外部库。
    • 动态库适用于需要在多个程序中共享且可能频繁更新的代码,因为它们的更新不会影响已经编译的程序。

    18. 整理所有用到的GCC指令

    GCC(GNU Compiler Collection)是一个常用的编译器集合,用于编译和链接 C、C++、Objective-C、Fortran 等语言的程序。以下是一些常用的 GCC 指令:

  1. 编译 C 源文件:

    gcc -o output_file source_file.c
    
  2. 编译 C++ 源文件:

    g++ -o output_file source_file.cpp
    
  3. 编译并链接多个源文件:

    gcc -o output_file source_file1.c source_file2.c
    
  4. 指定编译器优化级别:

    gcc -O2 -o output_file source_file.c
    
  5. 生成调试信息:

    gcc -g -o output_file source_file.c
    
  6. 指定链接库文件:

    gcc -o output_file source_file.c -lm
    
  7. 生成汇编代码:

    gcc -S -o output_file.s source_file.c
    
  8. 查看 GCC 版本信息:

    gcc --version
    

19. gdb命令

使用 GDB(GNU Debugger)调试程序时,可以使用一些常用的快捷键来方便操作。以下是一些常用的 GDB 调试快捷键:

  1. 运行程序

    • r:运行程序(run)。
  2. 断点

    • b:设置断点(breakpoint)。
    • d:删除断点(delete)。
  3. 单步执行

    • n:单步执行(next,逐过程)。
    • s:单步执行(step,逐语句)。
  4. 查看变量

    • p var:打印变量的值(print)。
    • display var:持续打印变量的值。
  5. 查看堆栈

    • bt:查看函数调用堆栈(backtrace)。
  6. 查看源代码

    • list:显示当前位置附近的源代码。
  7. 控制程序

    • c:继续执行程序(continue)。
    • q:退出 GDB。
  8. 其他

    • help:显示帮助信息。
    • Ctrl + C:中断程序执行。

20. 说明一下使用动态库和静态库的区别。

使用动态库和静态库的主要区别在于链接方式和运行时的行为。以下是它们的区别:

  1. 静态库(Static Library)
    • 静态库在链接时会被完整地复制到可执行文件中,因此可执行文件会变大。
    • 静态库的代码是静态链接的,意味着库的代码在编译时就被链接到可执行文件中,因此可执行文件在运行时不需要外部库的支持。
    • 静态库适用于需要在多个项目中共享且不频繁更新的代码,因为它们的更新需要重新编译整个程序。
  2. 动态库(Dynamic Library)
    • 动态库在程序运行时才被加载到内存中,并被多个程序共享,因此可以节省内存。
    • 动态库的代码是动态链接的,意味着库的代码在运行时才被加载到内存中,因此可执行文件需要依赖外部库。
    • 动态库适用于需要在多个程序中共享且可能频繁更新的代码,因为它们的更新不会影响已经编译的程序。

21. 动态库链接的顺序有影响吗?下面两种链接方法,假设第一个指令能执行成功,第二个指令一定能成功吗?

bash 复制代码
gcc a.c -o a -ltesta -ltestb
gcc a.c -o b -ltestb -ltesta

动态库链接的顺序在某些情况下可能会影响链接的结果。在一般情况下,-l选项用于指定链接的库,gcc会按照指定的顺序搜索库文件,并将它们链接到可执行文件中。如果两个库有相互依赖关系,那么它们的链接顺序就变得重要了。

对于给定的两个库libtesta.solibtestb.so,如果libtesta.so依赖于libtestb.so,那么第一个指令应该能够成功链接,因为-ltesta-ltestb之前,所以libtesta.so会被链接在libtestb.so之前。
但是第二个指令可能会出现链接错误,因为-ltestb-ltesta之前,所以libtestb.so会被链接在libtesta.so之前,这可能导致链接错误,因为libtesta.so需要libtestb.so中定义的符号。

总的来说,动态库的链接顺序是有影响的,应该根据库之间的依赖关系来决定链接顺序。

相关推荐
光亮§那方17 分钟前
linux - cp 命令
linux·ubuntu
liulanba20 分钟前
leetcode--二叉树中的最长交错路径
linux·算法·leetcode
蜗牛学苑_武汉23 分钟前
Linux之文本三剑客
linux·运维
来世做春风嘛27 分钟前
第二十一章 网络编程
服务器·网络·php
WaiSaa1 小时前
Linux内核升级
linux·运维
白如意i1 小时前
在Ubuntu 16.04上安装和配置Elasticsearch的方法
linux·ubuntu·elasticsearch
stackY、1 小时前
【Linux】:命令行参数
linux
Feel_狗焕1 小时前
Linux下GDB调试一篇入魂(GDB调试详解)
linux·debug
不知 不知1 小时前
CentOS中使用SSH远程登录
linux·centos·ssh
l258036911211 小时前
Keepalived
linux·运维