Linux程序设计2023 期末复习

Linux基础

  1. Linux是根据GNU通用公共许可证开发的一种免费Unix类型的操作系统

  2. 1991年Linus Torvalds编写了第一个版本的Linux内核

  3. 安装、开机、硬件、BIOS、LILO、软件

  4. linux分区

image-20241215220213507
  1. GRUB:GRand Unified Bootloader
image-20241215220222170
  1. linux文件类型

    • 普通文件
    • character special file
    • block special file
    • 网络接口 socket
    • 符号链接 symbolic link
    • 目录
  2. 目录结构:所有目录存储在一个虚拟的统一文件系统中

    • 物理设备挂载在挂载点上(软盘、硬盘分区、CD-ROM驱动)
  3. 基本命令:

    • pwd:print working directory 打印工作目录
    • cd:切换目录
    • mkdir:创建目录
    • rmdir:移除目录
    • ls:列出目录内容
    • touch:修改文件访问时间或文件修改时间(不存在时会创建)
    • cp:拷贝文件
    • mv:移动和重命名文件
    • ln:链接文件
    • rm:移除文件
    • cat:打印文件内容
    • more/less:按页展示文件
  4. 文件权限:

    • 三种访问级别:User、Group、Others

    • 三种全写:read、write、execute

    • 查询文件权限:ls -l

    • 修改权限:示例:chmod u+x filename

    • 另一种办法:

      image-20241215220237192
    • 默认权限:文件 -rw-r–r– 644;目录 drwxr-xr-x 755

  5. 进程:进程是一个正在执行的程序实例,由执行程序、它的当前值、状态信息以及通过操作系统管理此进程执行情况的资源组成。

    • 所有进程都是由其他进程启动的,即父子关系,除了init(PID 1)由内核自己启动
    • 进程可以由自己或其他进程的信号关闭
  6. image-20241215220343297
  7. 根目录下目录:

    • /bin:存放系统的基本命令
    • /boot:内核、bootloader的配置,包括引导加载程序相关的文件
    • /dev:包含设备文件,这些包括终端设备、USB或连接到系统的任何设备
    • /etc:系统的配置文件
    • /home: 存放用户主目录
    • /lib:系统库
    • /mnt:作为暂时挂载文件系统的目录,通常用于挂载其他文件系统
    • /proc:存放内存中的虚拟文件系统,提供有关系统和进程的信息
    • /root: 存放超级用户(root)的主目录
    • /sbin:存放系统管理员使用的命令
    • /tmp:存放临时文件,该目录下的文件通常会在系统重启后被删除
    • /usr:资源文件夹,存放用户程序和文件,包括系统软件、应用程序、库文件和文档等
    • /var:存放系统和应用程序的变量数据,例如日志文件、缓存文件和邮件文件等
    • /media: 存放可移动设备(如U盘、CD/DVD等)挂载的目录
    • /srv: 存放服务的数据目录,例如网站的文件目录
    • /opt: 存放第三方软件的安装目录
  8. image-20241215220403590

    image-20241215220410597

  9. 重定向:

    • 0:标准输入,1:标准输出,2:标准错误
    • <:重定向输入
    • >:重定向输出,>>:重定向追加输出
    • 2>
  10. 管道:一个进程的输出作为另一个进程的输入

    1
    2
    3
    ls | wc -l
    ls -IF | grep^d
    ar t /usr/lib/libc.a | grep print | pr -4 -t
  11. 正则表达式

Shell编程

基础
  1. Shell定义:用户和操作系统之间的接口,作为核外程序而存在

  2. 执行脚本:

    • sh script_file 新建进程
    • chmod +x script_file 新建进程
    • ./script_file 新建进程
    • source script_file 使用当前进程
    • . script_file 使用当前进程
  3. 用户环境:.bash_profile .bash_logout .bashsrc这三个文件

  4. 用户变量:用户在shell脚本里定义的变量

    1
    2
    var=value 或
    read var
  5. read命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    echo -n "Enter your name:"  #-n:不换行 
    read name
    echo "hello $name, weclome to my program"
    exit 0

    read -p "Enter your name:" name #-p:直接在read中指定一个提示

    if read -t 5 -p "Enter your name:" name #-t:指定一个回复时间5秒
    then
    echo "hello,$name"
    else
    echo "too slow"
    fi
    exit 0

    read -n1 -p "Do you want to continue [Y/N]?" answer #-n1:限制输入长度为1

    read -s -p "Enter your password:" password #-s:输入不显示在屏幕(字体与背景同色)
  6. 引号

    • 单引号会保持字符原本含义
    • 双引号会解析 $ `` \
  7. 环境变量:Shell环境提供的变量。通常使用大写字母做名字

    image-20241215220437244

  8. 参数变量和内部变量

    image-20241215220445022

shell语句
  1. 字符串比较

    1
    2
    3
    4
    str1 = str2
    str1 != str2
    -z str #字符串空时为真
    -n str #字符串不空时为真
  2. 算数比较

    1
    2
    3
    4
    5
    6
    exp1 -eq exp2
    exp1 -ne exp2
    exp1 -gt exp2 #大于
    exp1 -ge exp2
    exp1 -lt exp2 #小于
    exp1 -le exp2
  3. 文件条件测试

    1
    2
    3
    4
    5
    -e file	#文件存在
    -d file #目录
    -f file #普通文件
    -s file #长度不为零
    -r file -w file -x file #读写可执行
  4. 逻辑操作

    1
    2
    3
    !exp
    exp1 -a exp2 #and
    exp1 -o exp2 #or
  5. if语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if ["$answer" = "yes"];then
    echo "yes"
    elif ["$answer" = "no"];then
    echo "no"
    else
    echo "sorry"
    exit 1
    fi
    exit 0
  6. case语句

    1
    2
    3
    4
    5
    case str in
    str1 | str2 | str3) statements1;;
    str4 | str5) statements2;;
    *) statements6;;
    esac
  7. for语句

    1
    2
    3
    4
    for file in $(ls f*.sh);do
    lpr $file
    done
    exit 0
  8. while语句和until语句

  9. select语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    clear
    select item in Continue Finish
    do
    case "$item" in
    Continue);;
    Finish) break;; #只有进入break才会退出
    *) echo "Wrong choice!";;
    esac
    done
  10. 命令表

    • command1;command2;… 按顺序执行,前面失败后面继续执行
    • command1 && command2 && … 按顺序执行,前面成功才能继续执行
    • command1 || command2 || … 按顺序执行,前面失败才会执行下一个
  11. 语句块 { }

  12. 函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    yesno(){
    msg="$1"
    def="$2"
    while true; do
    echo ""
    echo "$msg"
    echo $def
    done
    return $def
    }
    调用函数:func para1 para2
  13. 杂项命令

    image-20241215220457877
  14. 算数扩展 $((…))

    1
    2
    x=0
    x=$(($x+1))
  15. 参数扩展

    1
    2
    3
    4
    5
    6
    7
    批处理文件 1_tmp,2_tmp,...
    i=1
    while ["$i" -ne 10];do
    touch "${i}_tmp"
    i=$(($i+1))
    done
    exit 0

    image-20241215220507696

编译链接

  1. image-20241215220518696 image-20241215220532093
  2. GCC/G++ options

    • -E:到预处理
    • -S:到编译
    • -c:到汇编
    • 不加-E-S-c时,到链接
    • -o:指定输出文件名
    • -g:产生调试工具必需的符号信息
    • -O/On:在程序编译、链接过程中进行优化处理
    • -Wall:显示所有的警告信息
    • -Idir: 指定额外的头文件搜索路径
    • -Ldir: 指定额外的库文件搜索路径
    • -lname: 链接时搜索指定的库文件
    • -DMACRO[=DEFN]: 定义MACRO宏
  3. 文件后缀

    • .c:C源码
    • .h:C头文件
    • .i:经过预处理的C源码
    • .s:汇编代码
    • .S:未预处理的汇编代码
    • .o:目标文件(汇编后)
    • .a:静态库文件
    • .so:动态库文件
    • .exe:可执行文件
  4. GDB

  5. 静态库与动态库

    • 二者的不同点在于代码被载入的时刻不同:

      • 静态库的代码在编译过程中已经被载入可执行程序,因此体积比较大。
      • 动态库(共享库)的代码在可执行程序运行时才载入内存,在编译过程中仅简单的引用,因此代 码体积比较小。
      • 不同的应用程序如果调用相同的库,那么在内存中只需要有一份该动态库(共享库)的实例。
      • 静态库和动态库的最大区别:静态情况下,把库直接加载到程序中,而动态库链接的时候,它 只是保留接口,将动态库与程序代码独立,这样就可以提高代码的可复用度,和降低程序的耦 合度。
    • 静态库:编译链接时,把库文件的代码全部加入可执行文件中,因此生成的文件比较大,但是运行 时也就不需要库文件了,后缀名一般为 .a

      • 为什么需要静态库:通过静态库的方式降低复杂度,在升级更新时尽量做到增量更新,但是静 态库会导致复用性降低,磁盘占用高。
    • 动态库:在编译链接时并没有把库文件的代码加入可执行文件中,而是在程序执行时由运行时链接 文件加载库,这样可以节省系统的开销,后缀名一般为 .so

      • 动态库的作用: 库文件不在可执行文件中,放置在外侧;升级更新会方便快捷;动态库会存在冲突(版本问题)
    • gcc/g++在编译时默认使用动态库。无论静态库还是动态库,都是由.o文件构成的

make & Makefile

  1. 格式:make [-f Makefile] [option] [target]

  2. hello : main.o kbd.o 
    gcc -o hello main.o kbd.o 
    main.o : main.c defs.h
    cc -c main.c
    kbd.o : kbd.c defs.h command.h
    cc -c kbd.c 
    clean :
    rm edit main.o kbd.o
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    3. 执行次序:

    - make会在当前目录下找名字叫“Makefile” 或 “makefile” 的文件
    - 查找文件中的第一个目标文件(target),举例中的hello
    - 如果hello文件不存在,或是hello所依赖的文件修改时间要比hello新,就会执行后面所定义的命令来生成hello文件
    - 如果hello所依赖的.o文件不存在,那么make会在当前文 件中找目标为.o文件的依赖性,如果找到则再根据那一个 规则生成.o文件。(类似一个堆栈的过程)
    - make根据.o文件的规则生成 .o 文件,然后再用 .o 文件生成hello文件

    4. 伪目标

    - “伪目标”并不是一个文件,只是一个标签,所以make无法生成它的依赖关系和决定它是否要执行,只能通过显示地指明这个“目标”才能让其生效
    - “伪目标”的取名不能和文件名重名
    - 为了避免和文件重名的这种情况,可以使用一个特殊 的标记“.PHONY”来显示地指明一个目标是“伪目标 ”,向make说明,不管是否有这个文件,这个目标就是“伪目标”
    - 伪目标一般没有依赖的文件,但也可以为伪目标指定所依赖的文件
    - 伪目标同样可以作为“默认目标”,只要将其放在第一个

    5. 多目标:当多个目标同时依赖于一个文件,并且其生成的命令大体类 似,可以使用一个自动化变量“$@”表示着目前规则中所有的目标的集合

    ```makefile
    bigoutput littleoutput : text.g
    generate text.g -$(subst output,,$@) > $@
    等价于
    bigoutput : text.g
    generate text.g -big > bigoutput
    littleoutput : text.g
    generate text.g -little > littleoutput
  3. 预定义变量:

    • $< 第一个依赖文件的名称
    • $? 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚
    • $+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
    • $^ 所有的依赖文件,以空格分开,不包含重复的依赖文件
    • $* 不包括扩展名的目标文件名称
    • $@ 目标的完整名称
    • $% 如果目标是归档成员,则该变量表示目标的归档成员名称
  4. 函数

    • $(subst from,to,text) 替换text中from为to
    • $(strip string) 去除首尾空格和多余空格
    • $(dir names…) 获取文件的目录名部分
    • $(basename names) 去除文件后缀名后部分
    • $(foreach var,list,text) 对list中元素执行text操作
    • $(if condition,then-part,else-part) 条件语句
    • $(call expression,parm1,parm2,….) 创建一个函数

文件系统

基础
  1. VFS:Virtual File system Switch :采用标准的Unix系统调用读写位于不同物理介质上的不同文件系统, 使得open()等系统调用不用关心底层的存储介质和文件类型
  2. 组成:
    • 超级块:某一个磁盘的某一个分区的文件系统的信息,记录了文件系统类型和参数
    • i-node对象::记录真正的文件,文件存储在磁盘上时是按照索引号访问文件的,软 链接是不同的文件,但是硬链接是相同的inode号,同一个文件
    • 文件对象:记录了文件描述符、索引号,不对应真正的文件,文件打开会创建出文件对 象,文件关闭才会释放内核中的文件对象。记录了文件的读写状态
    • 目录对象::维护了目录中的逻辑关系,若要通过目录来查找文件,都需要这个对 象。在路径上,无论是目录还是文件,都是一个dentry对象对应到目录包含的i-node上,目录项包 括索引节点编号,目录项名称长度以及名称
  3. 硬链接:hard link
    • 不同的文件名对应于同一个inode
    • 不能跨越文件系统
    • 对应系统调用link
  4. 软连接:symbolic link
    • 存储被链接文件的文件名(而不是inode)实现链接
    • 可跨越文件系统
    • 对应系统调用symlink
系统调用
  1. 系统调用:Linux内核的对外接口;用户程序和内核之间唯一的接口;提供最小接口

  2. 库函数:依赖于系统调用;提供较复杂功能;例如标准I/O库

  3. 文件描述符:

    • 一个非负整数: int fd;
    • 其中0为标准输入,1为标准输出,2为标准错误(即0、1、2被占用)
  4. open/create

    1
    2
    3
    4
    5
    6
    7
    8
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>

    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
    int open(const char *pathname, mode_t mode);
    //成功返回fd,失败返回-1

    flags为文件访问模式,有O_RDONLY,O_WRONLY,O_RDWR,O_APPEND,O_TRUNC,O_CREAT,O_EXCL

    mode为使用权限,如下:

    image-20241215220552303

    umask:一种文件保护机制,用法: mode & ~umask

  5. close

    1
    2
    3
    #include <unistd.h>
    int close(int fd);
    //关闭成功返回0,失败返回-1
  6. read/write

    1
    2
    3
    4
    5
    6
    #include <unistd.h>
    ssize_t read(int fd, void *buf, size_t count);
    //返回读到的字节数,读到文件尾为0,出错返回-1

    ssize_t write(int fd, const void *buf, size_t count);
    //成功返回已写的字节数,出错返回-1

    例:

    1
    2
    3
    4
    5
    6
    mycat.c
    while ((n=read(STDIN_FILENO, buf, BUFSIZE))>0)
    if (write(STDOUT_FILENO, buf, n)!=n)
    err_sys("write error");
    if (n<0)
    err_sys("read_error");
  7. lseek:重写定位读写偏移量

    1
    2
    3
    4
    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
    //成功返回结果偏移位置,失败返回-1

    whence有SEEK_SET(文件开始偏移)、SEEK_CUR(从现在位置开始偏移)、SEEK_END(文件结尾偏移)

  8. dup/dup2

    1
    2
    3
    4
    #include <unistd.h>
    int dup(int oldfd);
    int dup2(int oldfd, int newfd);
    //成功返回新fd,失败返回-1

    用于重定向

  9. fnctl:操作文件描述符

    1
    2
    3
    4
    5
    #include <unistd.h>
    #include <fcntl.h>
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, long arg);
    int fcntl(int fd, int cmd, struct flock *lock);

    cmd有F_DUPFD、F_GETFD/F_SETFD、F_GETFL/F_SETFL、F_GETOWN/F_SETOWN、F_GETLK/F_SETLK/F_SETLKW(获取/设置文件锁)

  10. ioctl

标准IO库
  1. File Steam

    image-20241215220606477

    Stream Buffering操作

    image-20241215220613960
  2. Stream open/close

    image-20241215220626467
  3. 输入单个字符:getc,fgetc,getchar

    image-20241215220643095
  4. 输出单个字符:putc,fputc,putchar

    image-20241215220654609
  5. 输入一行字符串:fgets,gets

    image-20241215220703948
  6. 输出一行字符串:fputs,puts

    image-20241215220711458
  7. 二进制流IO:fread/fwrite

    image-20241215220723773 image-20241215220742676
  8. 格式化IO:scanf,fscanf,sscanf

    image-20241215220750865
  9. 格式化IO:printf,fprintf,sprintf

    image-20241215220802935
  10. 重定位流:fseek(移动指针),ftell(获取指针位置),rewind(将指针移动到开头),fgetpos(获取指针位置),fsetpos(移动指针位置)

    image-20241215220814240
  11. 刷新流:fflush

    image-20241215220822188
  12. 流与文件描述符

  13. 临时文件

    image-20241215220830699
文件属性
  1. 获取文件status:stat,fstat,lstat(前两个对于链接文件,返回实际文件信息,第三个返回链接自身信息)

    image-20241215220840282
  2. stat结构:

    image-20241215220848006
  3. SUID、SGID、Sticky bit

    • SUID:会认为文件的执行者是文件拥有者(而不是执行的人),这样运行该程序的用户就可以以文件拥有者的身份执行程序,从而获得文件拥有者的特权。如 su
    • SGID:认为文件的执行组是文件所在组(而不是执行的人所在的组);对于一个目录,在该目录下创建文件,会认为是目录拥有者创建。如 sudo
    • Sticky bit:用于防止用户删除其他用户拥有的文件或目录,只有该目录的拥有者、文件拥有者和root用户才能删除该目录中的文件。

    image-20241215220856267

  4. 测试文件权限:access

    image-20241215220902850
  5. 改变文件权限:chmod/fchmod

    image-20241215220910401
  6. 改变文件所有者:chown/fchown/lchown

    image-20241215220918010
  7. 设置文件权限屏蔽字:umask

    image-20241215220923096
  8. 硬链接:link/unlink

    image-20241215220927867
  9. 软连接:symlink/readlink

    image-20241215220932873
目录处理
  1. 目录结构:

    image-20241215220937914
  2. 创建与删除目录:mkdir/rmdir

    image-20241215220943151
  3. 切换工作目录:chdir,fchdir

    image-20241215220948318
  4. 获取路径:getcwd

    image-20241215220952735
  5. 打开、关闭、读、定位

    image-20241215220957614
  6. 示例:目录扫描

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Dir *dp;
    struct dirent *entry;

    if ((dp=opendir(dir)) == NULL)
    err_sys("wrong");
    while ((entry = readdir(dp)) != NULL){
    lstat(entry->d_name,&statbuf);
    if (S_ISDIR(statbuf.st_mode))
    ...
    else
    ...
    }
    closedir(dp);
文件锁
  1. 文件锁分类

    • 记录锁:按记录加锁(可以只锁文件的一部分)
    • 劝告锁:检查、加锁由应用程序自己控制,不会强制应用程序不允许访问,只是提醒
    • 强制锁:检查、加锁由内核控制,影响open、read、write
    • 共享锁:可以读
    • 排他锁:读写均不可
  2. flock结构

    image-20241215221004865
  3. 记录锁:fcntl

    image-20241215221014328
  4. lockf

    image-20241215221024483

linux内核

  1. image-20241215221030804
  2. 层次结构

    image-20241215221037276
  3. 驱动:

    • 许多常见驱动的源代码集成在内核源码里
    • 也有第三方开发的驱动,可以单独编译成模块.ko
    • 编译需要内核头文件的支持
  4. 加载模块

    • 底层命令
      • insmod(装载一个模块,只有超级用户可以使用该命令)
      • rmmod(卸载一个模块)
    • 高层命令
      • modprobe(相当于多次insmod,自动加载依赖)
      • modprobe -r (-r:模块闲置不用时,自动卸载模块)
  5. 模块依赖

    image-20241215221045121
  6. 模块通讯:模块是为了完成某种特定任务而设计的。 其功能比较的单一,为了丰富系统的功能 ,所以模块之间常常进行通信。其之间可以共享变量,数据结构,也可以调用对方提供的功能函数。

  7. 模块相关命令:

    image-20241215221051531
  8. Linux内核模块与应用程序的区别

    image-20241215221055939
  9. 注意点:

    • 不能使用C库来开发驱动程序
    • 没有内存保护机制
    • 小内核栈
    • 并发上的考虑
  10. 例子,最简单的内核模块例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/init.h>

    static int __init hello_init(void){
    printk(KERN_INFO "Hello world\n");
    return 0;
    }

    srarcit void __exit hello_exit(void){
    printk(KERN_INFO "GoodBye\n");
    }
    module_init(hello_init);
    module_exit(hello_exit);
    image-20241215221102434

openEuler操作系统

  1. 鲲鹏处理器:基于ARMv8-64指令集开发的通用处理器,作为一款现代处理器,它早已经不是仅仅包含ALU的运算单元了。在其物理架构上包含SoC、Chip、DIE、cluster、 core等概念。

    • 鲲鹏920具有L1、L2、L3共三级cache,L1和L2两级cache由各个CPU core独享

    • 鲲鹏处理器支持CPU Core虚拟化、内存虚拟化、中断虚拟化以及SMMU等多项虚拟化技术。

    • 鲲鹏处理器与openEuler操作系统有着非常紧密的联系,openEuler天然地支持鲲鹏 处理器,并能够充分发挥处理器的各种特性。

  2. openEuler出现于2019年年底,是一款通用服务器操作系统,支持x86和ARM等多种处理器架构, 适用于数据库、大数据、云计算、人工智能等各种应用场景。

  3. openEuler是一款基于Linux内核的通用操作系统;为了充分发挥鲲鹏处理器的优势,openEuler在多核调用技术、软硬件协同、轻 量级虚拟化、指令级优化和智能优化引擎等方面做了增强。

  4. 多核调度:openEuler使用了先进先出、轮转调度、优先级调度以及CFS 调度算法:

    • CFS调度算法使用了时间片和优先级的概念,并且引入了虚拟运行时间,使得操作系统按照当前系统的负载和普通进程的优先级给该进程分配CPU使用的比例,从而确保了普通进程的相对公平
    • 在openEuler中,每个处理器都有一个迁移线程(称为migration/CPUID),每个迁移线程都有一个由函数组成的停机工作队列
  5. NUMA-aware Qspinlock:openEuler采用CAN(Compact NUMA-aware Lock)队列代替Qspinlock中的MCS队列:

    • CNA队列是MCS队列的一种变体
    • MCS将等待获取锁的线程组织在一个队列中,而CNA则将等待获取锁的线程组织为 两个队列:一个主队列,一个辅助队列
    • 主队列的线程队头运行在相同的NUMA节点上。辅助队列的线程与主队列队头运行 在不同NUMA节点上
  6. KAE:openEuler通过提供鲲鹏加速引擎(Kunpeng Accelerator Engine,KAE)插件,使能 Kunpeng硬件加速能力,包括:

    • 对称/非对称加密
    • 数字签名
    • 压缩解压缩等算法,用于加速SSL/TLS应用和数据压缩
  7. iSula:iSula为全量的容器软件栈,包括引擎、网络、存储、工具集与容器OS:

    • iSulad是一个轻量级容器引擎,采用C/C++语言实现,相比其它容器引擎,它的内存 开销更小,并发性能更高
    • iSulad容器引擎主要包括通信模块、镜像模块、运行时模块
  8. A-Tune:自调优工具A-Tune旨在让操作系统能够满足不同应用场景的性能诉求,降低性能调优过程中反复调参的人工成本,提升性能调优效率

    • A-Tune整体上是一个C/S架构
    • A-Tune目前主要提供智能决策和自动调优两个能力