System Programming
System Programming

System Programming

Tags
Learning
Computer System
Author

Chapter 1 Linux Basis

Linux 简介

GNU: free software + Unix-like OS project, whose kernel is kernel of Linux
GNU + Linux = GNU / Linux OS
composition of Linux OS:
  • Kernel
  • shell(用户与计算机交流接口)
  • file structure
  • tools

Linux命令

I/O 重定向:将输出和输入重定向至其他形式(如:stdout 变成 a.txt)
管道:左边的输出作为右边的输入
eg. 终止掉所有名称中带有task的进程:
ps -A | grep task | awk ‘{print $1}’ | xargs kill -9
重定向:把I/O设备改成别的($ cat < inputFile)
 
Special Linux commands:
  • touch:改变文件的时间标签,或者创建一个新文件
  • nl:在显示文件(非文件则是标准输出)时添加行号
xargs
awk
sed
grep
ps
kill
 
 
 

Chapter 2 Shell

Shell Basis

shell:命令解释器,可以用于启动、停止、编写程序
是一个用户与Unix/Linux OS内核之间的交互接口
也就是说写完一堆装有机器级代码的脚本文件,然后OS将其交给Shell后Shell解释运行这堆命令
作用
  • 主要功能:解释用户在命令提示符下输入的命令
  • 提供个性化的用户环境(在Shell的初始化文件完成)
  • 是一门解释性的编程语言(Linux命令+程序逻辑结构)
 
Linux Standard Shell — Bash
支持:向下(OS)兼容,作业(job,相当于一串指令以一定逻辑构成的程序)控制
 
Shell功能
  • 解释命令行
  • 启动命令(让内核运行命令)
  • I/O重定向
  • 管道连接
  • 变量维护(定义、使用)
  • 环境控制(环境变量:包含OS里一个或多个程序会共同用到的信息)
 

Shell编程基础

执行方法:
  • ch <scriptName> (不支持stdin的读数据功能,不推荐)
  • bash <scriptName>
  • chmod u+x <scriptName>(更改这个文件的权限为user可执行的) 再 ./<scriptName>
结构:
  • 第一行:#!/bin/bash,表示使用的解释器路径
    • 不加#!会无法使用Shell内建的命令
    • 其他地方(脚本行)写#!会被当作普通注释
  • 退出:
    • exit (n),n = 0表示执行成功,反之
    • 若脚本无exit:脚本返回状态由最后一条命令的执行状态决定
    • $?读取最后执行命令的退出码
    • 退出码的含义:0表示成功,1-125用户可自己定义,126是文件不可执行,127是没有找到相关命令,128表示接收到一个信号
  • 脚本注释:#(#!是个例外)
  • 逗号:链接一系列算数操作,结果只保留最右边一个表达式的
 
变量
  • 环境变量:定义和系统工作环境相关的变量
  • 用户变量
    • 定义变量时,变量名前不需要加$
    • 注意定义的时候不能在赋值号左右加上无意义的空格,会认为变量名是个命令
    • 不声明变量类型
    • 只可读的:readonly <varietyname>
    • 使用且与别的东西相连时:${name}
    • notion image
      notion image
  • 内部变量
    • 只能使用而无法被修改或定义
    • 就是一堆$开头的东西,比如$#, $*, $$(当前进程的进程号)
    • $@和$*都是当前脚本的外部参数,区别在于前者用了IFS作为划分符
  • 引号们
    • 单引号内所有字符都当作普通字符
    • 双引号内除了$ \ ‘ “ 四个字符以外其他都当作普通字符
    • 倒引号`所括内容先被执行,然后其结果代替原命令的位置
  • 位置参数变量
    • 在文件内部用的,代表外面运行时传递的一堆参数
    • $ ./test a b c
    • $n(n from 0 to 9)就是文件名和后面9个参数
    • shift命令可以让2-9位置的参数分别变成1-8然后右边再加一个9位置的参数(重新分配命令参数位置,除了$0全部左移)
  • 置换参数
    • 用途:根据参数的不同情况来给变量赋值
    • name=${parameter:-default} : 当parameter已被设置则用parameter赋值,否则用default赋值
    • = : 同-,再加上一个给parameter也赋一个default
    • +:如果parameter已设置,则用default置换变量,否则不进行置换而使用null字符串
    • ? :如果没有设置parameter,则显示default并从Shell中退出
  • Bash变量的使用
    • 不区分数据类型
    • 定义依赖上下文
    • 可不可以做算术和比较操作取决于变量是否只由数字构成
  • $(command)命令型变量的使用风格
    • 等价于倒引号,更推荐使用小括号
表达式
  • $((exp)):表示表达式的计算( eg. a=$(($a+1)) )
  • $(…):表示执行指令或者获取输出
  • expr命令:将它的参数作为一个表达式进行求值
    • eg. x = $(expr $x + 1) or `expr $x + 1`
      • 注意!!算术运算时一定要把运算数和运算符分开
    • 注意:*要用转义符写成 \*
  • test condition 或者 [condition] 进行条件测试
    • 字符串、数值比较
    • 文件操作
    • 逻辑操作
  • 条件表达式
    • 字符串比较
      • = / != 表示相等与否
      • 单个变量:表示变量是否为空
      • -n str 当str长度大于0时返回真
      • -z str 长度==0
    • 整数比较
      • int1 -le/ge/eq/ne(not equal)/gt/lt int2
    • 文件操作(见PPT)
    • 逻辑操作
      • !exp
      • exp1 -a exp2 (and)
      • exp1 -o exp2 (or)
  • 字符串
    • 字符串长度
      • ${#str}
      • expr length $str
      • expr “$str” : ‘.*’ #注意冒号前后都有空格
    • 正则表达式
      • 从str开始位置开始匹配其有多少个字符符合regex的格式:
        • expr match ${str} ${regex}
        • expr ${str} : ${regex}
        • notion image
          notion image
      • 在str里找substr第一次出现的位置
        • expr index str substr
      • 提取子串
        • ${str:position}
          • 如果str是@或者*: 提取从position开始的位置参数
        • ${str:position:length}
          • 从position位置开始提取length长度的子串
            • position默认从0开始
            • length > 0: 就是长度; length < 0: 变成了最后一个字符的index
          • ${str:(-5):(-3)}: 从倒数第五个开始取,取到倒数第三个。记得要加()
        • expr substr $str $position $length(同上)
      • 削除子串
        • ${str%substr} 削除第一个匹配的
        • ${str%%substr} 削除最后一个匹配的
      • 子串替换
        • ${str/substr/replacement}
          • 用replacement换掉str里的第一个substr
        • ${str//substr/replacement}
          • 用replacement换掉str里的所有substr
        • ${str/#substr/replacement} → /%
          • 替换前缀/后缀
流程控制
  • 分支流程
    • #注意!左右方括号与内容之间要有一个空格!里面的参数和参数提示符(-le什么的)也要有 if [ $1 -le 10 ];then #... elif [ $1 -le 20 ];then #... fi
       
      case variable in expr1) #case expr1 ;; expr2) #case expr2 ;; *) #default ;; esac
  • 循环流程
    • while [ ... ];do #... done
      until [ ... ]; do #if ... == false: do these commands done
      for arg in [list]; do #every cycle: take variables in list in sequence and store it in arg done
 
函数
#define [ function ] <funcname> () {funcbody} [redirection] #funcbody里用“位置参数”的形式访问参数,$0指的是函数名 #必须在调用前定义 #call funcname <parameters list>
 
数组
#define declare -a var_name #define1 a[11]=23 #index 11 of array: 23, others are initialized with NULL #define2 a=( XXX YYY ZZZ... ) #initialize from 0 #define3 a=([idx1]=XXX, [idx2]=YYY ...) #call echo ${a[11} #"{}" is needed
 
调试
sh -n scriptname
  • 检测脚本语法错误
-v
  • 执行命令前打印命令
-x
  • 打印每个命令的执行结果
 
trap命令
trap <command> <signal>
  • 必须放在脚本段的第一行(#!下面)
  • 捕捉程序对应的signal,一旦捕捉到就执行command
notion image
 
 
 

Chapter 3 Linux programming Env

gcc: GNU Compiler Collection

支持多种编程语言
 
gcc选项
  • 预处理
    • -E,直接将输入文件预处理后输出到stdout
    • -D name=value: 预定义程序中的name宏
  • 编译和警告
    • -S, 对输入文件进行预处理和编译处理
    • -c, 预处理、编译、汇编
    • 不带参数:一步到可执行文件
    • -Wall: 警告
  • 链接库:-I
    • -L dir:指定库所在的目录
    • -l library:指定库
    • -static:强制使用静态库
    • -shared:创建共享库
    • 静态库 V.S. 共享库
      • 静态库(.a .lib):编译时被链接到目标代码中参与编译,被链接时将库完整拷贝到可执行文件中。
        • 占用资源多(每个文件都要拷贝一份)
        • 不易于更新(每个使用静态库的程序都需要更新)
      • 动态(共享)库(.so .dll):程序运行时由系统动态加载到内存,供程序调用。

Chapter 4 File Operation

文件操作(系统调用):产生一个异常,将用户空间的文件信息传给内存,内存对其进行操作
 
ext2文件系统
notion image
Super Block:描述整个块组的信息,一般只有第一个Block Group有
Block Group: Block在逻辑上被分为一个个组
Inode:一种数据结构,存放文件的数据指针和各属性,存在了一个表里面
Dentry(目录项):包含目录之下的各文件的文件名和Inode号(索引,不是真的Inode)
 
操作
  • open操作:打开或创建一个文件
    • Prototype:
      • #include<fcntl.h> int open(const char* pathname, int flags, ...)
        flag: 表示文件的一些属性
        eg.
        notion image
         
    • 返回值:文件描述符(成功),-1(不成功)
      • 文件描述符:已打开文件的索引,通过该值可以在fd_array表中检索相应的文件对象
       
  • unlink操作:删除文件。若是软链接则删除链接,若不是则等待当前进程对其的调用结束之后再删掉。
 
文件描述符:已打开文件的索引
 
文件描述符属性控制:fcntl
 
文件锁:保护共享资源(主要是指线程)的一种机制
 
文件链接:硬链接和符号链接
  • 硬链接:使新文件与源文件指向同一个Inode,当且仅当每一个向Inode的指针都被删除则文件被删除。所有文件内容一致,改一个另一个也要被改
  • 软链接(符号链接):新文件指向的是源文件的“路径”,新文件与旧文件的Inode不同,且新文件Inode的内容是旧文件的路径。删除源文件后软链接失效,新文件也被清空。但是软链接依然是存在的,访问软链接的新文件并编辑后源文件又会复活
  • 用ln命令进行链接操作,删除链接就直接删新文件就可以
 
文件权限:
  • st_mode
    • 0-8: user, group, others访问权限(顺序为rwx),也对应9个字母的表示:rwxr--r--,即st_mode = 111100100
    • 9-11: 用户ID,组ID,文件粘住位
    • 12-15:7种文件类型
  • umask:四位八进制数,后三位表示当前系统默认禁用的权限位(高到低分别对应user, group, others)
  • 在设置权限的时候,会自动减去umask
    • eg. 创建权限为777的文件,umask0245,则文件的权限变为(777 - 245) = 532
 
文件扩展名:OS用于标识文件类型的机制。
  • 在Linux种,除GCC等一些编译器以外,Linux并不通过文件扩展名,而是文件头部信息来标识文件类型。文件执行权限和文件扩展名无关。
 
文件I/O
  • 标准文件I/O:不依赖于系统内核(so可移植性强),仅对缓冲区进行操作,当缓存区满足一定条件之后再执行系统调用。
    • 好处是减少了系统调用及对块设备(以块为单元的设备)的读写操作
  • 系统调用I/O:直接跟操作系统交互。
    • 好处是少拷贝一次内存,提高系统效率
 

Chapter 5 Process Management

进程:计算机分配资源的单位
Linux进程的主要类型:
  • 交互进程:由Shell启动的进程,在前台面对用户
  • 守护进程:一堆后台进程,主要在后台干活,与终端无关
  • 批处理进程:多个进程,与终端无联系
 
进程创建:pid_ fork(void)函数,头文件#include <unistd.h>
返回值:-1: 失败
0: 说明此时进入了子进程
> 0: 此时还在父进程里,返回子进程的进程序号pid
子进程与父进程是相同的一份代码,但是子进程开始的时间点在fork函数之后,即进程变成父与子进程后依次运行
fork调用一次,返回两次
 
代码的全局数据段只是针对此进程的全局变量
exec族函数:调用内部的可执行文件,也就是说执行一个Linux的shell命令
 
进程退出: exit() or _exit()
前者调用退出处理函数,然后清除I/O缓存(保证进程的东西写到磁盘上)
后者不干这两件事儿,强制执行exit系统调用
 
终端:I/O设备的抽象
守护进程不会被exit,一直存在
守护进程与终端无关,在一般进程所在的终端被关闭的时候被创建
进程组:多个进程的集合,由进程组PID表示
控制进程:一个终端的控制进程为由它发起的一系列进程的组长进程
会话期:由终端发起的一个或多个进程组的集合,在终端运行期间所有进程属于会话期
创建守护进程:
  1. 父进程退出,创建子进程
  1. 在子进程中创建会话期(setsid();)
  1. 将当前目录更改为根目录(chdir(“/”);)
  1. 重设文件掩码(umask(0);)
  1. 关闭文件描述符(?)
 
僵尸进程:子进程退出而父进程还没退出,且父进程由于没有苏醒等原因没有回收子进程的资源,则子进程变成僵尸进程,没有任何代码和数据,但是会占一个进程位
 
pid_t wait(int* status)暂停父进程,等待子进程结束并获取子进程返回状态
status: 保存子进程的返回状态
pid_t: 子进程的pid
 
进程返回状态:成功,失败,死亡
status被分为三个部分:exit value, core dump flag, signal number
 
waitpid():非阻塞式wait,用于并发程序,判断子进程完事儿没
notion image
 
重定向
tty:进程的终端设备的抽象接口
程序默认将结果写到文件描述符1(stdout),错误信息写到2(stderr)
 
Shell来做重定向,而不是程序
重定向输出:将默认文件描述符 > 其他文件描述符
如果不想输出到任何地方:重定向到/dev/null
1 (2)>filename:stdout > filename
重定向符号和文件名不传给文件名
 
打开文件的时候:系统为进程安排的描述符为此进程对应的struct_fd数组里最小可用的fd
 
stdin重定向到文件
dup(int fd):将最小可用的fd指向fd指向的文件(有种硬链接的美)
dup2(int oldfd, int newfd): 关掉newfd然后直接再dup(oldfd)(感觉不一定返回的就是输入的newfd)
方法一:close(0)然后open(filename, O_RDONLY),自动给它分配到最小可用的fd:0
方法二:fd = open重定向目标文件,然后close(0),再dup(fd),然后close(fd)
 
管道通信
管道特点:单向单工,字节流FIFO传输
命名管道:任意进程
匿名管道:父子进程
notion image
父子进程共用一个struct_fd数组,并且会自动在子进程里复制现有管道