失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 【北航软院】系统编程学习笔记

【北航软院】系统编程学习笔记

时间:2018-07-21 13:19:36

相关推荐

【北航软院】系统编程学习笔记

当时复习了整整一周,可以说是写得最认真的笔记了,结果发现才两学分(然后没空复习数据库了)

文章目录

1.Linux命令基础1.1 文件1.2 路径1.3 Linux命令目录操作命令文件操作命令文件备份和压缩命令1.4 VI编辑器1.5 Linux命令行命令总结1.6 POSIX标准和LSB标准2.GCC/GDB/Makefile2.1 GCC2.2 GDB调试器2.3 Make工具与Makefile2.4 静态库与动态库3.Shell编程3.1 Linux权限3.1.1 rwx权限3.1.2 Linux用户3.2 重定向3.2.1 标准输入输出3.2.2 重定向3.3 管道3.4 环境变量3.4.1 短期设置环境变量(在进程中,退出Terminal消失)3.4.2 永久设置环境变量3.4.3 C语言程序获取环境变量3.4.4 path环境变量3.5 shell编程3.5.0 shebang3.5.1 特殊字符3.5.2 变量3.5.3 shell条件表达式3.5.4 shell字符串操作3.5.5 条件控制语句3.5.6 循环控制语句3.6 Shell其他命令3.6.1 echo命令3.6.2 shift命令3.6.3 shell函数3.6.4 shell数组3.7 Bash调试4.文件I/O操作4.1 文件操作函数4.2 目录操作函数4.3 链接文件4.3.1 硬链接4.3.2软链接/符号链接4.4 文件属性更改4.5 标准文件IO4.6 错误处理函数4.7 管道和重定向4.7.1 重定向例题:重定向代码分析5 Linux进程管理5.1 进程的概念5.2 进程的创建和命令执行5.2.1 fork5.2.2 exec函数家族5.3 进程退出5.3.1 退出5.3.2 守护进程5.3.3 僵尸进程5.3.4 进程退出状态6.信号6.1 信号的概念6.2 信号的分类6.2.1 可靠与不可靠6.2.2 实时与非实时6.3 信号的处理6.3.1 `signal`信号处理机制6.3.2 `sigaction`信号处理机制6.3.3 信号阻塞函数`sigpromask`6.4 信号发送6.4.1 命令行6.4.2 函数:6.5 可重入函数6.6 父子进程的信号处理6.7 系统定时信号6.8.1 睡眠函数 sleep6.8.2 定时函数 alarm6.8.3 计时器 itimerval + setitimer7 进程间通信7.1 管道7.1.1 无名管道7.1.2 命名管道7.2 System V信号量7.3 POSIX有名信号量7.4 共享内存7.4.1 POSIX共享内存API7.4.2 mmap共享内存机制7.5 消息队列8 线程8.1 线程的相关操作8.1.1 线程创建8.1.2 线程终止8.1.3 线程挂起8.1.4 获得自身线程号8.2 互斥量8.3 信号量8.4 条件变量其他命令行传参的方法strcpy

1.Linux命令基础

1.1 文件

①最大长度不超过256个字符,可以含有字母、数字、._-,不能含有/

②通配符:*表示匹配0或多个字符;?表示匹配任何一个字符;[ab1A-F]表示匹配任何一个列举在集合中的字符。

1.2 路径

①绝对路径:从根目录开始的路径,以/开头,如/home/user/test

②相对路径:从当前目录开始的路径,不以/开头

.表示当前目录,..表示当前目录的父目录,/是根目录或路径中的分隔符

./test/a.c

1.3 Linux命令

目录操作命令

$ ls [选项] [文件名/目录名]#查看目录及文件$ ls -a #列出所有文件$ ls -d #列出所有目录$ ls -l #列出文件的详细信息

$ pwd #显示用户的当前工作目录

$ cd [目录名] #切换目录#如果不输入目录名,即返回用户主目录#如果目录名输入`-`即返回上一级$ cd /home/downloads #例子$ cd a #进入a目录

$ mkdir [参数] 目录名 #创建目录$ rmdir [参数] 目录名 #删除目录#删除目录时要保证目录内已无任何文件或目录,否则命令不成功#参数-p表示递归删除目录或创建目录,例子如下$ mkdir -p linux/doc/fedora #表示首先在当前目录下创建linux目录,然后在linux目录下创建doc目 #录。然后在doc目录下创建fedora目录$ rmdir -p parent/child #删除parent目录下的child目录,如果删除之后parent目录也为空,那么 #将parent目录也删除

文件操作命令

$ cat [文件名称]#显示文本文件内容$ cat file1 #显示file1的内容$ cat file1 file2>file3 #把file1和file2的内容输入到file3中

$ cp [文件] [目的地] #拷贝文件$ cp /root/* /temp #将root下的所有文件(不包括隐藏文件)拷贝到根目录下的temp内

$ mv [文件名1] [文件名或目录名] #文件更名或删除

$ rm [文件名] #删除文件

$ find [目录名] [选项] #搜索文件或目录$ find . -name "*.c" #列举当前目录及其子目录下所有扩展文件名是'.c'的文件#.是目录名,表示在当前目录下查找#-name "*.c"是选项,-name表示根据文件名查找,"*.c"给出了文件名的格式#选项还可以是-print表示将匹配文件输出到标准输出#-size 表示根据文件大小查找#-user 表示根据文件所有者查找#-exec表示对匹配的文件执行该选项所给出的命令,命令格式为'command' {}\;例子如下$ find .-name "*.c" -exec ls -l {}\;

$ grep [字符串] [文件名] #在文件中查找字符串$ grep tigger file1#在file1中查找tigger字符串$ grep -v -n printf 2.6.c#-v表示在文件中查找不包含printf的行,-n表示将该行的行号显示出来

文件备份和压缩命令

$ tar cvf [打包的文件名] [要打包的文件]$ tar cvf test.tar *.txt #将当前目录下所有txt文件打包为text.tar

$ gzip [选项] [文件目录列表] # 压缩命令$ gunzip [选项] [压缩包包名] # 解压缩命令# gzip压缩后不保留源文件$ zip [-r] [压缩后的文件名称] 文件或目录 # 压缩# -r表示压缩的是一个目录$ unzip [选项] 压缩包包名 # 解压缩# zip压缩后保留源文件

1.4 VI编辑器

启动:在终端输入命令vivim即可启动VI编辑器。

三种状态:命令模式、插入模式和可视模式。

命令模式:可以在VI编辑器下方输入命令,如保存、查找和替换。

插入模式:对文本内容进行插入、删除、添加操作。

可视模式(底行模式):提供了友好的选取文本范围并显示高亮的功能。

切换:

用 vi 打开文件后默认进入命令模式。在命令模式输入操作符i后进入插入模式,输入esc退出插入模式,返回命令模式;在命令模式下输入:/进入底行模式,输入esc退出底行模式,返回命令模式。

命令模式下的命令:

:w 将文件内容保存到vi命令所指定的文件中:w filename 将文件另存为filename:wq! 将文件内容保存到vi命令所指定的文件中然后退出vi:wq! filename 以filename为文件名保存后退出:q! 不保存直接退出:x 保存并退出,功能和:wq!相同:q 退出命令,如果文件内容有更改,VI会提示需要保存才能退出

jkhl分别代表向下、上、左、右移动

通过插入命令可以由命令模式进入插入模式,如iaIAo都可以。

在命令模式下按v可以进入可视模式。

1.5 Linux命令行命令总结

# 路径$ /home/user #绝对路径$ ./test/h.c #相对路径$ ../user/test/h.c #也可以写作从上一级目录开始的相对路径# 文件操作命令$ ls #查看当前目录下的所有文件$ ls /home/user/test #查看指定目录下的所有文件$ pwd #查看当前位置的绝对路径$ mkdir #创建目录$ cd /home/user/test #切换到指定目录$ cd#切换到根目录$ cd . #切换到上一级目录$ cd .. #切换到上两级目录 ???cd -???$ touch a.txt #创建文件$ cp [源路径] [目的路径] #复制文件$ mv [源路径] [目的路径] #移动文件$ rm [目标路径] #删除文件或目录$ rmdir [路径] #删除目录# 要删除的目录必须为空,否则无法删除# -r 递归删除,即可以删除指定目录及其子目录# -f 强制删除,不管目录是否为空# rm -rf [目标路径] 最强效的删除命令$ cat ./test/h.c #查看文件内容$ cat h.c | more #分页查看文件内容,但只能向前移动$ cat h.c | less #加强版more,可以向后移动$ head -n 10 h.c #查看前10行$ tail -n 10 h.c #查看后10行$ which [命令] #查看某一命令所在路径$ find 搜索路径 [选项] 关键字# -name按文件名查找# -size按文件大小查找# -user按文件所有者查找$ locate #查找,功能类似find -name# 压缩和解压缩命令#--zip格式$ gzip [选项] 文件$ gunzip [选项] 压缩包名#gzip压缩不保留源文件$ zip [-r] [压缩后文件名称] 文件或目录$ unzip [选项] 压缩包名#压缩时保留源文件,[-r]表示压缩的是一个目录#--tar格式$ tar cvf tmp.tar.gz * # 打包,*表示当前目录下的所有文件#如果选项中有-z则为压缩,如果没有就只是打包$ tar -xvf tmp.tar.gz -C tmp #解压,-C表示命名解压后的目录# 网络管理命令$ ip addr$ ifconfig # 查看IP地址$ ping #检查网络是否连通#软件安装命令$ wget [选项] [URL地址] #下载$ sudo apt-get install package #安装#vi编辑器的使用$ vi filename #打开filename文件或创建一个名为filename的新文件$ vi #创建一个新文件并打开,文件名在保存时指定命令模式/插入模式/底行模式(可视模式)命令模式:打开VI编辑器后默认进入k 或 ↑j 或 ↓h 或 ←l 或 →b 光标移动到本单词的首字符e 光标移动到本单词的尾字符插入模式:在命令模式下输入i进入插入模式底行模式:在命令模式下输入:或/进入底行模式:set nu 设置行号:set nonu 取消设置行号yy复制当前行p将复制的内容粘贴到当前字符的下一个位置:n 移动光标到第n行删除当前字符:x删除当前行:dd删除当前行在内的n行:ndd/查找的关键词 #查找指定内容,n 下一个,N 上一个:s/被替换内容/替换内容/#替换当前行的第一个目标:s/被替换内容/替换内容/g#替换当前行的全部目标:%s/被替换内容/替换内容/g #替换整个文档的全部目标:%s/被替换内容/替换内容/gc #替换整个文档的全部目标,替换每个 #内容时都询问:wq #保存并退出:wq[filename] #保存为filename并退出:q #退出:q! #强制退出:w testfile2 #另存为filename2

补充:

# 用命令找出当前目录下的空目录$ find -type d -empty# -type d 指的是查找的是目录

1.6 POSIX标准和LSB标准

POSIX 表示可移植操作系统接口,定义了操作系统应该为应用程序提供的接口标准,是 IEEE 为要在各种 UNIX 操作系统上运行软件而定义的一系列 API 标准的总称。 POSIX 并不局限于 Unix 系统,Microsoft windows NT 等也支持 POSIX 标准。

LSB 是 Linux 标准化领域中事实上的标准,使各种软件可以很好地在兼容 LSB 标准的系统上运行。 LSB 以 POSIX 和 SUS 标准为基础,还增加了对二进制可执行文件格式规范的定义,保证 Linux 上应用程序源码和二进制文件的兼容性。

2.GCC/GDB/Makefile

2.1 GCC

GCC相关命令

gcc test.c无选项编译链接,作用将 test.c 预处理、编译、汇编并链接形成可执行文件。这里未指定输出文件,默认输出为 a.out。

GCC将一个源程序转换为可执行文件的步骤:源文件->预处理->编译->汇编->链接->可执行文件

-o指定输出的可执行文件的名字

-E预处理选项,gcc -E test.c -o test.i将test.c预处理为test.i文件

-S编译选项,gcc -S test.i将test.i文件编译成test.s文件

-c汇编选项,gcc -c test.s将test.s文件编译成test.o二进制文件

gcc test.otest.o链接生成可执行文件test

gcc -E test.c -o test.i #预处理gcc -S test.c -o test.s #编译gcc -c test.s -o test.o #汇编gcc test.o -o test #生成可执行文件

2.2 GDB调试器

在用gcc编译程序时,加上-g参数,这样产生的可执行文件具有调试信息,才能使用GDB进行调试。

进入GDB模式:

GDB [可执行程序名称]先输入GDB进入调试模式,再输入file [filename]加载需要调试的程序,最后用run或r命令开始执行,也可以用run parameter的方式传递参数。

退出GDB:输入quit或者按Ctrl+D

GDB命令

l#显示多行源代码break/b [函数名/行号] #设置断点i#描述程序的运行状态r#开始运行程序disp [变量名] #跟踪查看某个变量,每次停下来都显示它的值s#执行下一条语句,遇到函数则进入函数执行该函数的第一条语句next#执行下一条语句,不进入函数print/p [变量名] #打印变量值c#继续执行程序直到遇到下一个断点set var #设置变量的值start#开始执行程序,在main函数第一条语句前面停下file #装入需要调试的文件kill/k#终止正在调试的程序watch#监视变量值的变化backtrace/bt #查看函数调用的信息info locals #查看所有局部变量的值frame/f #查看栈帧quit/q#退出GDB环境

2.3 Make工具与Makefile

安排系统先编译哪个再编译哪个,叫做构建(build)。Make工具是最常用的构建工具,使用Make工具构建程序时,构建规则被写在一个叫做Makefile的文件中。

一是通过输入的指令,比如 make test,则会根据 test 的依赖关系进行迭代生成二是当发现待生成的文件已经存在时,会判断其依赖项是否发生了更新,如果没有更新则不再重复生成

2.4 静态库与动态库

静态库:命名方式为libxxx.a,以lib开头,.a结尾,xxx是静态库文件的文件名

动态库:命名方式为libxxx.so,以lib开头,.so结尾,xxx是动态库文件的文件名

区别

静态库的代码是在编译过程中被载⼊程序中;动态库在编译的时候并没有被编译进⽬标代码,⽽是当你的程序执⾏到相关函数时才调⽤该函数库⾥的相应函数。

因此,对于相同的程序,使用静态库生成的可执行文件要比使用动态库的内存开销大。

如果所使⽤的静态库发⽣更新改变,你的程序必须重新编译;动态库的改变并不影响你的程序,动态函数库升级⽐较⽅便。

静态库和程序链接有关和程序运行无关;动态库和程序链接⽆关和程序运⾏有关。

如果链接完之后删掉静态库,程序仍然能正常运行,如果删掉动态库源文件,程序就不能正常运行了。

链接方法

生成静态库文件(将mytool1.c和mytool2.c这两个文件编译,并用生成的目标文件mytool1.o和mytool2.o创建生成libmylib.a静态库)

gcc -c mytool1.c -o mytool1.ogcc -c mytool2.c -o mytool2.oar cr libmylib.a mytool2.o mytool1.o

链接静态库(首先将main.c编译生成了可执行文件main,同时在编译的时候链接静态库libmylib.a)

gcc -o main main.c -L. -lmylib

生成动态库文件(将mytool1.c和mytool2.c这两个文件编译,并用生成的目标文件mytool1.o和mytool2.o创建生成libmylib.so动态库)

gcc -c -fPIC mytool2.c -o mytool2.ogcc -c -fPIC mytool1.c -o mytool1.ogcc -shared -o libmylib.so mytool2.o mytool1.o

链接同上gcc -o main main.c -L. -lmylib

可能出现的简述题:静态库和动态库的区别(命名方式,文件大小,载入的时间,删除后还能不能正常使用,链接方式)

3.Shell编程

3.1 Linux权限

3.1.1 rwx权限

当在命令行输入ls -l时,Linux系统会列出目录下文件的详细信息。

第一个字段就是文件权限

如drwxrwxr-x

第一个字符表示文件的类型:

-表示普通文件,d表示目录,l表示链接文件等

其余九个字符表示这个文件的详细权限,三个字符为一组 ,总共有三组,分别代表了拥有者,拥有组,其他人对这个文件的权限。

r表示读权限,w表示写权限,x表示执行权限

x代码为1,w代码为2,r代码为4。故7表示rwx,6表示rw-,等等。

3.1.2 Linux用户

Linux的用户分为管理员和普通用户,普通用户又分为系统用户和自定义用户,用户信息可以在/etc/passwd中查看。

· 管理员/root账户:拥有所有系统权限

· 系统用户:Linux系统为满足自身管理所内建的账号

· 自定义用户:由root管理员创建的用户

Linux中每个用户必须属于一个用户组,用户组信息可以在/etc/group/中查看。

su # 切换到root用户sudo + 指令# 使本条指令以最高权限运行chmod [权限代码] 文件名 # 更改文件权限useradd -g 所属组名 用户名 # 添加用户groupadd 组名 # 添加组chown [选项] 用户名 文件名 #更改文件所有者chgrp [选项] 组名 文件名 # 更改文件所属组passwd [选项] 用户名 #为用户设置密码userdel/groupdel [选项] 用户名/组名 #删除用户/组exit #退出root用户

3.2 重定向

3.2.1 标准输入输出

Linux中默认的标准输入输出分为三个文件:

标准输入stdin,文件号是0

标准输出stdout,文件号为1

标准错误stderr,文件号为2

(所以分配其他文件的文件号时从3开始)

3.2.2 重定向

重定向的格式:命令 [输入输出流向符号] 文件名

输入重定向:命令 < 文件名

输出重定向:命令 > 文件名

追加重定向:命令 >> 文件名

错误重定向:命令 2 > 文件名

比如:gcc -c test.c -o test.out 2> error.log。当执行gcc编译出现错误时,不会输出到屏幕上,而是输出到error.log中

&运算符:等同于2>&1,表示将标准错误重定向到标准输出指向的文件

3.3 管道

将左边命令的输出作为右边命令的输入

比如ls /etc | grep initgrep的作用查找字符串中是否包含某字符串,这个管道命令的意思是查找/etc目录下文件名包含init的文件/目录

例题:请写出命令who | wc -l的结果并分析其执行过程。

1;因为who命令用于显示系统中的使用者有多少,who输出了1行,wc命令用于输出行数,将who命令的输出作为wc命令的输入,则结果为1.

3.4 环境变量

3.4.1 短期设置环境变量(在进程中,退出Terminal消失)

export [环境变量名]=[值]如:export system_programming=cool查看环境变量:echo $system_programming

3.4.2 永久设置环境变量

修改~/.bashrc文件(.表示该文件是隐藏文件,要用ls -a才能看见)。

gedit ~/.bashrc打开~/.bashrc文件,在末尾加上export STUDENT_ID="19373178",保存退出。

再用echo $STUDENT_ID查看即可看到环境变量的值

这种方法修改的环境变量只有当前用户能读取到

如果想修改结果让所有用户都能读取到,可以将export语句写到/etc/profile文件

3.4.3 C语言程序获取环境变量

获取所有环境变量

#include<stdio.h>extern char** environ;int main(){char **p = environ;for(; *p != NULL; p++)printf("%s\n", *p);return 0;}

获取指定环境变量

#include<stdio.h>#include<stdlib.h>int main(){const char* envName = "envName";printf("$envName=%s\n", getenv(envName));return 0;}

3.4.4 path环境变量

存储系统默认的查找可执行文件的路径,是多个路径用:连接起来的,比如:

/usr/local/sbin:/usr/local/games:/snap/bin

3.5 shell编程

bash是最常用的一种shell

#! /bin/bashecho 'Linux is cool!'

3.5.0 shebang

#!是shebang符号,表示用什么命令来执行该文件,比如.sh文件,由于开头用了shebang,因此在命令行直接输入文件名系统就知道应该用什么方法来解释该文件。

如果开头的shebang改为#! /bin/rm,则系统只会用rm来解释该文件,即文件一运行就会被删除。

如果开头的shebang改为#! /bin/more,则系统只会用more来解释该文件,即运行文件会将文件打印在终端上。

执行bash脚本命令

chmod 764 test.sh #或 chmod +x test.shtest.sh #或 ./test.sh

3.5.1 特殊字符

转义字符/:当希望使用特殊字符的字面义时

单引号'':单引号所包含的所有字符保持字面意义(单引号不能嵌套)

双引号"":使除了$ / `之外的其他所有字符保持字面义,意思是:

​ $ 和`两个字符保持特殊含义

​ 而转义字符/只有后面跟的是$ `` " 或换行符的时候才有转义的特殊作用

string=helloecho $string#输出helloecho '$string' #输出$stringecho "$string" #输出helloecho "\string" #输出\stringecho "\$string" #输出$string,$在/的作用下失去特殊含义

命令替换符``或$():在shell编程中,用该命令的执行结果替换该命令

命令连接符&&||语义同C语言

3.5.2 变量

Shell用户变量:使用时无需创建,不分类型,统一认为是字符串,需要的时候进行类型转换。采用$变量名${变量名}的方式使用。

Shell环境变量

Shell内部变量

# 参数数量* 所有参数输出方式:echo $*当*在双引号中时,即"$*"时,字符串扩列为"$1c$2c…",其中c为IFS变量值的第一个字符? 上一条命令的执行返回值! 最后一条命令的进程号$ 当前的进程号0 当前执行的shell程序的名称@ 所有参数,"$@"被扩列为"$1"、"$2"、……_ shell程序启动时为正在执行的shell程序的绝对路径,之后为上一条命令的最后一个参数

shell变量的参数扩展

变量=${parameter:-word}:如果parameter未定义或为null,则用word置换变量的值,否则用parameter置换变量的值。变量=${parameter:=word}:如果parameter未定义或为null,则用word置换parameter的值然后置换变量的值,否则用parameter替换变量的值。变量=${parameter:?word}:如果parameter未定义或为nullword被写至标准出错(默认情况下在屏幕显示word信息)然后退出,否则用parameter置换变量的值。变量=${parameter:+word}:如果parameter未定义或为null,则不进行替换,即变量值也为null,否则用word置换变量的值。

shell变量的算术扩展

$((…))将需要计算的表达式包含在其中,用计算结果替换expr 表达式 计算并显示表达式的值

3.5.3 shell条件表达式

格式:if [ $i -lt 10 ]; then fitest 表达式 或者直接使用 [表达式]判断条件表达式真假字符串操作:-z String 若String长度为0,表达式为真-n String 若String长度不为0,表达式为真String1 = String2 若String1与String2相等,表达式为真String1 != String2 若String1与String2不相等,表达式为真逻辑操作:!exprexpr1 -a expr2 与运算expr1 -o expr2 或运算数值比较:-eq //equals等于-ne //no equals不等于-gt //greater than 大于-lt //less than小于-ge //greater equals大于等于文件操作:-d filename //若文件为目录,则为真-f filename //若文件为普通文件,则为真-r/w/x filename //若文件可读/可写/可执行,则为真

3.5.4 shell字符串操作

字符串长度

${#string}expr length $stringexpr "$string" : '.*'

输出正则表达式匹配的子串的长度

expr match "$string" "$substring"expr "$string" : "$substring" # 其中substring是一个正则表达式

正则表达式

. 一个除换行符之外的其他任意字符* 前一个字符匹配0次或多次[] 中括号中的字符都可以{3} 表示前一个字符应当重复3次| 或运算\ 转义符如:'^([0-9]{3}-|\([0-9]{3}\) )[0-9]{3}-[0-9]{4}$'可以匹配 (xxx) xxx-xxxx 或 xxx-xxx-xxxx 的字符串

字符串索引

expr index $string $substring#输出string中第一次匹配到sbustring中某一个字符的位置如:string=abcABC123ABCabc则:echo `expr index "$string" 12c` #输出3

提取子串

${string:position} #在string中从position位置开始提取子串${string:position:length} #在string中从position开始提取长度为length的子串expr substr $string $position $length #在string中从position开始提取长度为length的子串expr match $string '\($substring\)' #提取能匹配正则表达式substring的子串

字符串删除

${string#substring} #从右边截掉第一个匹配substring的子串${string##substring} #从右边截掉最后一个匹配substring的子串${string%substring} #从左边截掉第一个匹配substring的子串${string%%substring} #从左边截掉最后一个匹配substring的子串

字符串替换

${string/substring/replacement} #用replacement替换第一个匹配的substring${string//substring/replacement} #用replacement替换所有匹配的substring${string/#substring/replacement}#如果$substring匹配$string的开头部分,就用replacement替换$substring${string/%substring/replacement}#如果$substring匹配$string的结尾部分,就用replacement替换$substring

3.5.5 条件控制语句

if [ 表达式1 ]; then命令列表1;elif [ 表达式2 ]; then命令列表2;else [ 表达式3 ]; then命令列表3;fi

Shell中用0表示真,用非0表示假

case 变量 in表达式1)命令列表1;;;表达式2)命令列表2;;;……*)默认命令列表;;;esac

select 变量 in 菜单项;do命令列表;done

3.5.6 循环控制语句

until 条件表达式;do命令列表;done

until语句:表示一直执行命令列表中的命令直到条件表达式为真(与while正好相反)

while 表达式;do命令列表;done

while语句:当表达式为真时,就一直执行命令列表中的命令

for name in [参数列表];do命令列表;done

for语句:按照顺序访问参数列表中的值,然后执行命令列表

3.6 Shell其他命令

3.6.1 echo命令

echo [选项] [参数]-n 表示显示内容后不换行,如果没有这个参数,则换行-e 解释参数中的转义符-E 不解释参数中的转义符

3.6.2 shift命令

shift [n] #参数向左移动n位

3.6.3 shell函数

shell程序是按行执行的,所以shell函数必须在使用前定义。

function max(){blabla……}max 1 2 3

3.6.4 shell数组

下标从0开始,数组定义后大小没有限制,且其数据可以不连续

array[index]=value #将array数组的index位置赋值为valuearray=(jerry tom alice keven julie)则array[0]=jerry,array[1]=tom,array[2]=alice……用array[index]访问数组,如果index是*或@,则意思是取数组的所有元素declare -a array通过declare命令声明数组array

3.7 Bash调试

bash的调试方法:

大量使用echo语句显示程序内部执行情况

使用trap命令捕获程序退出的信号并执行相应的动作

#! /bin/bashtrap "echo a=$a, b=$b, c=$c" EXIT#该语句的意思是在程序退出时打印a,b,c变量

使用sh命令的-n -x -v参数,如-n用于检测程序内部的语法错误;-x用于打印每条命令的结果;-v在执行程序中的每条命令前,显示该命令。

可能出现的简述题:

Linux中有哪些种类的文件?

普通文件,管道文件,链接文件,目录文件,块文件

shell编程中有哪些特殊字符

| 管道字符, $ 取变量, # 注释,连接符 ;,输入输出重定向 > <,\ 转义符

bash调试的方法

4.文件I/O操作

4.1 文件操作函数

(系统调用文件IO)

头文件

#include<fcntl.h>

#include<unistd.h>

#include<sys/types.h>

1. int fd = create(char *filename, 0777); //创建2. int fd = open(char *filename, int how, mode_t mode); //打开how:O_RDONLY 只读O_WRONLY 只写O_RDWR 可读可写O_APPEND 追加模式/在将数据写入文件之前,自动将文件位置指针移动到文件末尾O_TRUNC 清空文件内容O_CREAT 如果文件不存在,就先创建一个O_NONBLOCK 对文件进行无阻塞读写操作,无阻塞就是程序在对文件读时不需要等待数据,如果没有数据,立即返回-1,不需等待3. int result = close(int fd); //0表示成功,-1表示失败4. ssize_t numread = read(int fd, void *buf, size_t qty);5. ssize_t result = write(int fd, void *buf, size_t amt);fd:文件描述符,buf存储读到的数据的内存空间,qty/amt是所需要读取/写入数据的字节大小write如果不是追加模式的,就会从指定的起始位置开始覆盖文件(覆盖是一个字符一个字符地进行的,类似换行是一个换行符,而不是一行)6. off_t oldpos = lseek(int fd, off_t dist, int base);dist指指针移动的距离,可以为正也可以为负base指指针从何处开始移动,可以为SEEK_SET,SEEK_CUR,SEEK_END,分别表示开始、当前、末尾执行成功返回移动之前的指针位置,失败返回-17. int res = unlink(char *file_path);删除指定文件8. int result = fcntl(int fd, int cmd);int result = fcntl(int fd, int cmd, long arg1, long arg2, …);F_DUPFD 复制文件描述符F_GETFD 获取文件描述符F_SETFD 设置文件描述符F_GETFL 获取文件当前模式F_SETFL 设置文件当前模式(比如设置其为追加模式)F_GETLK 获取文件当前拥有的锁F_SETLK 设置文件锁F_SETLKW 设置记录锁9. flock结构struct flock{short l_type; //锁的类型,有F_RDLCK,F_WRLCK,F_UNLCK//当fcntl参数为SETLK时,F_RDLCK表示设置读锁,F_WRLCK表示设置写锁,F_UNLCK表示解锁//当fcntl参数为GETLK时,F_RDLCK表示已有读锁,F_WRLCK表示已有写锁,F_UNLCK当前无锁short l_whence; //起始位置,有SEEK_SET,SEEK_CUR,SEEK_END三种off_t l_start; //偏移量off_t l_len; //从起始位置+偏移量开始锁住的字节数pid_t l_pid; //返回在指定位置拥有一个锁的进程ID}使用方法:struct flock lock;fcntl(fd, F_GETLK, &lock); //获取锁fcntl(fd, F_SETLK, &lock); //设置锁10. flock函数对文件加锁时加的是建议性锁fcntl函数既可以对文件加建议性锁,也可对文件加强制性锁,还能对文件的某一记录上锁,也就是记录锁。对于建议性锁来说,内核只提供加锁以及检测文件是否已经加锁的手段,但是内核并不参与锁的控制和协调。劝告锁是一种协同工作的锁。它对于协作进程(cooperating processes)已经足够了。

4.2 目录操作函数

头文件

#include<sys/types.h>

#include<dirent.h>

#include<unistd.h>

DIR *opendir(const char *dir_name); #打开目录struct dirent *readdir(DIR *dir); #读取目录,每次读取一个文件存储在dirent指向的地址中int closedir(DIR *dir); #关闭目录void seekdir(DIR *dir, off_t offset); #将目录dir的指针移动到offset指定的位置off_t telldir(DIR *dir); #返回dir当前指针位置void rewinddir(DIR *dir); #重置目录指针位置为起始位置int res = mkdir(char *pathname, mode_t mode); #创建目录,mode是权限模式int res = rmdir(char *pathname); #删除目录int res = chdir(const char *path); #切换进程工作目录int res = rename(const char* from, const char *to); #目录或文件重命名或者移动到新的位置char *getcwd(char *buf, size_t size); #得到当前工作目录

4.3 链接文件

头文件#include<unistd.h>

4.3.1 硬链接

除了名字之外,链接文件的inode、大小、链接数等与源文件完全相同,也就是说硬链接文件与源文件本质上是同一个文件,即相当于硬链接是给源文件添加了一个文件名,删除源文件后硬链接文件仍然可以正常使用,因为相当于只是删除了原来的文件名,文件还要别的名字,因此文件没有删除。ls -l操作输出的第二列是文件的硬链接数。

4.3.2软链接/符号链接

更像一个指向源文件的指针/Windows中的快捷方式,与源文件inode、大小等均不同,其中存储的数据本质上是源文件的路径。当源文件删除,软链接文件也不能继续使用。

#include<unistd.h>char *src = "/etc/passwd";char *hardlink = "/home/backup/passwd";char *softlink = "/home/cosmos/passwd";int status;status = link(src, hardlink);status = symlink(src, softlink);

ln -s srcfile softlink#srcfile源文件,softlink新创建的软链接文件ln srcfile hardlink# hardlink新创建的硬链接文件

4.4 文件属性更改

头文件#include<unistd.h>

int chown(const char *path, uid_t owner, gid_t group);int chmod(const char *path, mode_t mode);

4.5 标准文件IO

头文件#include<stdio.h>

FILE *fopen(const char *restrict filename, const char *restrict mode);int fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);fseekftellfwritefclose

4.6 错误处理函数

头文件#include<errno.h>

void perror(const char *s);//作用:当出现错误时,先打印字符串s,再将错误码对应的错误信息打印出来。

4.7 管道和重定向

4.7.1 重定向

freopen重定向输入输出流,用法如下:freopen("log.txt","w",stdout); //将标准输出stdout重定向写到“log.txt”中

dup函数:

new_fd = dup(old_fd)复制文件描述符old_fd到文件描述符new_fd

dup2函数:

fd1 = dup2(fd2, fd3),关闭fd2,并将fd3复制到fd1

一般这样用:new_fd = dup2(old_fd, new_fd),即如果new_fd现在是要一个打开的文件的文件描述符,先将new_fd关闭,然后将old_fd复制到new_fd

文件描述符的分配:最小可用原则(012都被占用,从3开始)

例题:重定向代码分析

有以下一段代码:

int fd1, fd2, fd3, fd4;fd1 = open("a.txt", O_RDONLY);fd2 = open("b.txt", O_WRONLY);fd3 = dup(fd1);fd4 = dup2(fd2, 0);

请问,最后的 fd1, fd2, fd3, fd4 的值为多少?并解释原因。

fd1 = 3fd2 = 4fd3 = 5fd4 = 0

当运行该程序时,进程自动为其打开三个文件描述符,即0、1、2.因此,根据最低可用文件描述符原则,open打开文件成功时,fd1应为3,fd2为4.当执行dup操作时,0~4文件描述符均已占用,因此将描述符3复制到描述符5,即fd3的值为5.而当进程执行dup2操作时,进程先将描述符0关闭,再将fd2文件描述符复制给fd4,即此时fd4文件描述符指向打开的文件b.txt,根据文件描述符最低可用原则,fd4的值为0而不是6.

简述题:

标准文件IO和系统调用文件IO的区别

标准文件IO是fopen fclose fread等函数,系统调用文件IO是open close read等函数。

系统调用 I/O 没有缓冲区,读写文件时,每次操作都会执行相关系统调用,这样处理的好处是直接读写实际文件,坏处是频繁地系统调用会增加系统开销标准文件 I/O 可以看成是在文件 I/O 的基础上封装了缓冲机制,先读写缓冲区,必要时再访问实际文件出,从而减少系统调用的次数

符号链接和硬链接的异同点

含义不同:符号链接是一类特殊的文件,其包含有一条以绝对路径或相对路径的形式指向其它文件或者目录的引用;硬链接是一个文件的一个或多个文件名删除文件性质不同: 在对符号链接进行读写时,系统会自动把该操作转换为对源文件的操作,但删除链接文件时,系统仅仅删除链接文件,而不删除源文件本身;如果目标文件被移动、重命名或删除,任何指向它的符号链接仍然存在,但会指向一个不复存在的文件而移动或删除原始文件时,硬链接不会被破坏,因为它所引用的是文件的物理数据 硬链接的文件不需要用户有访问原始文件的权限,也不会显示原始文件的位置

符号链接和硬链接在使用上的区别

补充:给出系统的umask码,以及用户希望执行权限代码,如何求最终的执行权限代码?

给出的umask码和用户希望的执行权限码都是八进制的,将其转为二进制,并将umask码取反,并进行与操作即可得到最终的权限码。

如:给出umask码为0022,用户希望的权限码为0765

则0022转为二进制为000010010,反码为111101101,0765转为二进制为111110101,进行与操作

1 1 1 1 0 1 1 0 1

1 1 1 1 1 0 1 0 1,得到

1 1 1 1 0 0 1 0 1,即745

5 Linux进程管理

5.1 进程的概念

三种类型:交互进程、批处理进程、守护进程

5.2 进程的创建和命令执行

5.2.1 fork

头文件#include<unistd.h>

执行过程:

①为子进程分配新的内存块和内核数据结构

②复制原来的进程信息到新的进程,即子进程和父进程具有相同的可执行代码

③向运行进程集添加新的进程

④fork执行结束后,向子进程返回0,父进程返回子进程的PID号。此后,两个进程可独立执行,执行顺序取决于进程调度。

**注意:**父进程和子进程的变量和内存不互通(所以缓冲区也是各有各的),子进程从父进程复制变量等信息后二者就没有关系了。

​ 但是存储在存储器中而非进程内存中的东西二者是公用的,比如系统中的文件读写操作,父子进程用的是一个文件,而且用的是一个文件位置指针,即当父进程修改了文件位置指针时,子进程的文件位置指针同步。

如下,无论是父进程先执行还是子进程先执行,二者的a不互相影响

a = 10;int pid = fork();if(pid == 0){//子进程a++;// a = 11}else{a--;// a = 9}

5.2.2 exec函数家族

execl(char *path, char *arg0, char *arg1, ……);execv(char *path, char *argv[]);execlp(char *path, char *arg0, char *arg1, ……);execvp(char *path, char *argv[]);

exec函数家族的作用是用特定代码替换子进程的代码,通过执行exec家族系列函数让子进程执行新的任务。

path表示可执行程序的名称或路径。

execlexecv的path要求用绝对路径

execlpexecvp的path可以只写文件名,由系统自动用$path环境变量补全路径

lv函数的传参方式不一样,l表示list,即参数是逐个列举的,v表示vertor,即将所有参数整体构造为指针数组传递。

lv的第一个参数都必须是可执行文件的名字。

list逐个列举的参数要在末尾加一个0表示参数列表的结束。

vertor的参数最后一个参数必须是NULL表示参数的结束。

**注意:**子进程执行exec系列函数就被“换脑”了,如果exec后面还有函数,子进程不会再执行。

5.3 进程退出

5.3.1 退出

进程的正常退出:

正常运行结束,进程自动消失;exit()退出;_exit()退出;

后两者的区别:exit()相当于 刷新缓冲中所有的流 + _exit()函数

进程的异常退出:

kill -9 pidNo

5.3.2 守护进程

特点:

守护进程是脱离于终端并且在后台运行的进程,它不受终端控制,即使终端推出,它仍然在后端运行。守护进程在执行过程中产生的信息不在任何终端显示,也不会被任何终端所产生的信息所打断。

将进程变为守护进程的步骤:

①创建一个新的子进程,然后让父进程退出

②让子进程脱离控制终端(调用setsid函数)

③改变当前目录为根目录(调用chdir("./")函数)

④修改文件权限掩码

⑤关闭文件描述符

5.3.3 僵尸进程

特点:父进程还没有结束,子进程就结束了,而且父进程没有使用wait系统调用获取子进程的结束状态时,子进程就成了僵尸进程。

如果父进程先结束,不会产生僵尸进程,而是会产生孤儿进程。

(父进程结束后子进程不会立即退出)

在后台执行一个会产生僵尸进程的程序,并用ps命令查看进程信息,能够发现僵尸进程后缀了一个<defunct>

5.3.4 进程退出状态

wait系统调用做两件事:

首先暂停父进程直到子进程结束,然后取得子进程结束时传给exit()的值,并存放在status中。

#include<sys/wait.h>#include<sys/types.h>pid_t pid = wait(int *status);//status指向一个保存子进程返回状态的整型变量//如果不存在子进程,返回-1//若有任意一个子进程结束,则返回该子进程的pid并保存其返回状态在status中,同时wait调用也结束pid_t pid = waitpid(pid_t pid, int *status, int options);//pid取-1,为等待任何一个子进程,与wait()同效//pid>0,等待进程号为pid的进程//pid=0等待其组ID等于调用进程的组ID的任何一个进程//pid<-1等待组ID等于pid绝对值的任何一个进程options选项:WNOHANG表示如果没有任何已经结束的进程则立即返回,不等待WUNTRACED表示如果子进程进入暂停执行情况则马上返回,但结束状态不予理会0:阻塞父进程,等待子进程结束返回值:如有错误,返回-1如果正常执行,则返回子进程的进程号,并将返回状态保存在status中,同上waitpid()调用结束

可能的简述题:

创建守护进程的步骤

6.信号

6.1 信号的概念

信号的作用:让进程知道发生了某种事件;根据该信号执行相应的动作(即它自己代码种的执行信号处理程序)

头文件:#include<signal.h>

信号用整型常量宏表示,以SIG开头,如CTRL+CSIGINTCTRL+/SIGQUIT,还有非法段访问信号、浮点数溢出信号等。

信号的状态:

递送delivery:进程对信号采取动作(执行信号处理函数或忽略)时称为递送;

未决pending:信号产生后和递送前的时间间隔为未决;

信号递送阻塞block:进程可指定对某个信号采用递送阻塞,如果此信号的处理方式为默认或捕捉,该信号就会处于未决态,直到进程解除对该信号的递送阻塞 或 信号处理方式变为忽略。如果该信号的处理方式是忽略,那么该信号永远不会处于递送或未决态,即被丢弃。

6.2 信号的分类

6.2.1 可靠与不可靠

可靠信号:多个信号同时出现时会排队,不会丢失

不可靠信号:多个信号同时产生时可能会丢失信号

6.2.2 实时与非实时

实时信号即可靠信号

非实时信号即不可靠信号

6.3 信号的处理

1.默认:信号的默认处理是终止进程

2.忽略:可以通过signal(SIGINT, SIG_IGN);函数使进程忽略某信号

3.捕捉并处理:除了SIGKILLSIGSTOP信号以外,其他信号都可以通过信号处理函数捕捉并处理。常见的信号处理机制有signalsigaction两种。

6.3.1signal信号处理机制

result = signal(int signum, void (*action));

signum是信号,action是处理方式。

其中action可以是SIG_DEF表示系统缺省处理,也可以是SIG_IGN表示忽略信号,还可以是自定义处理函数的函数名。

一个用signal函数让CTRL+C不退出进程而是输出信号值的实现:

#include<stdio.h>#include<signal.h>#include<unistd.h>#include<sys/types.h>void sigHandler(int signalNum){printf("The sign number is %d", signalNum);signal(SIGINT, sigHandler); //因为signal在改变一次信号处理方式之后会恢复默认,所以要在这里再写一遍信号处理函数}int main(){signal(SIGINT, sigHandler);while(1);//让进程一直进行而不停止return 0;}

当信号处理函数没有执行完的时候,如果又接收到同类型的信号,可能会发生信号的丢失。

当信号处理函数没有执行完的时候,如果又接收到不同类型的信号,程序会中断当前的信号处理,先处理该不同类型的信号,之后再继续处理上一个信号。

比如输入^C^C^C^\,可能会发生这种情况,首先执行第一个^C,此时第二个^C被阻塞,此时又收到^\,停止处理^C,执行^\,结束后继续执行第一个^C,然后执行第二个^C,第三个^C被丢弃。

6.3.2sigaction信号处理机制

result = sigaction(int signum, const struct sigaction *action, struct sigaction *prevaction);//signum表示信号;//action指向描述操作的结构的指针;//prevaction指向描述被替换操作的结构的指针;

struct sigaction{void (*sa_handler)();void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;}

sa_handler可以为SIG_DFLSIG_IGN或函数名

sa_sigaction是函数指针,有3个参数,第一个是接收到的信号,第二个是一个结构体,存储了信号相关的信息,比如产生的原因等。

sa_mask是一个信号集,表示如果在信号处理函数正在运行的时候,又有一个在sa_mask信号集中的信号产生了,则如果该信号是可靠信号,就将这个信号入队阻塞等待,如果这个信号是一个不可靠信号,就将这个信号丢弃

针对sigset_t信号集,有一组专门的函数对它进行处理

int sigemptyset(sigset_t *set); //清空信号集int sigfillset(sigset_t *set); //将所有信号填充进set中int sigaddset(sigset_t *set, int signum); //将信号signum添加到set信号集中int sigdelset(sigset_t *set, int signum); //将信号signum从set信号集中删除int sigismember(const sigset_t *set, int signum); //判断signum信号是否在信号集set中

sa_flags指示信号处理时应当采取的一些行为,可同时选多个

sigaction函数的用法

struct sigaction act;act.sa_sigaction = sighandler; //信号处理函数act.sa_flags = SA_SIGINFO; //设置为SA_SIGINFO时可以发送信息sigaction(SIGINT, &act, NULL);//接收信息的方式void int_Handler(int signo, siginfo_t *info, void *ucontext){printf("B recieve signal with message: %d\n", info->si_value.sival_int);}//info->si_value即sigqueue函数中的union signal value

6.3.3 信号阻塞函数sigpromask

int sigpromask(int how, const sigset_t *newset, sigset_t *oldset);//how有三种取值,SIG_BLOCK表示将new_set信号集中的信号加入进程的阻塞信号集合中,SIG_UNBLOCK表示将new_set信号集中的信号从进程的阻塞信号集合中拿出来,SIG_SET表示重新设置阻塞信号集合为newset//new_set//old_set表示保存原有的阻塞信号集

用法:

sigset_t newmask;sigemptyset(&newmask); //清空信号集sigaddset(&newmask, SIGINT);sigprocmask(SIG_BLOCK, &newmask, &oldmask); //阻塞sigprocmask(SIG_SETMASK, &oldmask, NULL); //恢复原来状态sigprocmask(SIG_UNBLOCK, &newmask, NULL); //解阻塞

sigprocmask函数阻塞可靠信号时,信号被放在队列中,解阻塞后依次执行。

sigprocmask函数阻塞不可靠信号时,解阻塞后只执行一次,其他都丢弃。

6.4 信号发送

6.4.1 命令行

kill -SIGRTMIN 3648//kill -[信号] 进程号

6.4.2 函数:

头文件:

#include<sys/types.h> #include<signal.h>

kill函数

int kill(pid_t pid, int sig);//参数:pid进程号,sig信号//返回值:-1遇到错误,0成功

raise函数:向自身进程发送信号

#include<sys/types.h>#include<signal.h>int raise(int sig);//sig被发送信号//返回值:-1遇到错误,0成功

sigqueue函数

int sigqueue(pid_t pid, int sig, const union sigval value);//参数:pid目标进程id//sig被发送信号//参数value是一个整型和指针类型的联合体//如果信号处理函数用sigaction注册,且sa_flags包含SA_SIGINFO选项,系统就会把value传递给信号处理函数union sigval{int sival_int;void *sival_ptr;};//返回值:-1遇到错误,0成功//接收信息的方式void int_Handler(int signo, siginfo_t *info, void *ucontext){

printf(“B recieve signal with message: %d\n”, info->si_value.sival_int);

}

//info->si_value即sigqueue函数中的union signal value

6.5 可重入函数

如果某个函数被多个任务并发使用时不会造成数据错误,则称该函数是可重入的。

6.6 父子进程的信号处理

当父进程创建子进程时,子进程继承了父进程处理信号的方式,直到子进程调用exec函数改变其代码。

子进程在退出之前会向父进程发送SIGCHLD信号,默认情况下父进程会忽略该信号,如果让父进程在该信号的信号处理函数中调用wait函数或waitpid函数获取子进程的退出状态,就可以达到防止僵尸进程产生的效果。

#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<unistd.h>#include<sys/wait.h>#include<unistd.h>void sigchld_handler(int sig){int status;waitpid(-1, &status, WNOHANG);if(WIFEXITED(status))printf("child process exited normally\n");else if(WIFSIGNALED(status))printf("child process exited abnormally\n");else if(WIFSTOPPED(status))printf("child process is stopped\n");elseprintf("else\n");}int main(){pid_t pid;signal(SIGCHLD, sigchld_handler);pid = fork();if(pid == 0){abort(0); //子进程调用abort异常退出}else if(pid > 0){sleep(2); //等待子进程退出printf("parent process\n");}elseexit(0);}

6.7 系统定时信号

6.8.1 睡眠函数 sleep

#include<unistd.h>unsigned int sleep(unsigned int seconds);void usleep(unsigned long usec);//第一个单位是秒,第二个单位是微秒

sleep函数内部是用信号机制实现的,其系统调用包括一个alarm函数和一个pause函数。

即在定时的时间到达时,alarm函数产生一个SIGALRM信号,而pause函数将进程挂起。

因为sleep内部是用alarm实现的,因此sleepalarm最后不要混用,以免造成混乱。

6.8.2 定时函数 alarm

alarm(t); //定时talarm(0); //取消定时

6.8.3 计时器 itimerval + setitimer

Linux系统为每个进程设计了三个计时器,分别是真实计时器、虚拟计时器和实用计时器。

真实计时器计算的是程序运行的实际时间虚拟计时器计算的是程序运行在用户态所消耗的时间(真实时间-系统调用切换和程序睡眠时间)实用计时器计算的是程序处于用户态和处于内核态所消耗的时间(真实时间-和程序睡眠时间)。

函数用法:

struct itimerval{struct timeval it_interval; //重复时间间隔struct timeval it_value;//初始时间间隔};struct timeval{long tv_sec; //秒long tv_usec; //微秒};//作用:定时地向进程发送SIGALRM信号//用法:int t = 1;struct itimerval p;p.it_interval.tv_sec = t;p.it_interval.tv_usec = 0;p.it_value.tv_sec = t;p.it_value.tv_usec = 0;setitimer(ITIMER_REAL, &p, NULL);//第一个参数是计时器类型,TIMER_REAL指真实计时器,ITIMER_VITUAL是虚拟计时器,ITIMER_PROF是使用计时器

7 进程间通信

7.1 管道

特点:单向且方向确定;数据在管道中以字节流的形式传送;

7.1.1 无名管道

只能在父子进程间通信。

#include<unistd.h>int pipe(int pipe[2]);//pipe[0]是读端的文件描述符//pipe[1]是写端的文件描述符

popen用法

FILE *fd = popen("command", "r");//即可以通过指针fd读“command”的执行结果FILE *fd = popen("command", "w");//即可通过指针fd向“command”命令写入数据

7.1.2 命名管道

可以在任何进程间通信

#include<sys/types.h>#include<sys/stat.h>int fd = mkfifo(const char *filename, mode_t mode);int fd = mknode(const char *filename, mode_t mode | S_FIFO, (dev_t) 0 );//mode为O_WRONLY、O_RDONLY、O_RDWR等//使用时直接用read和write读写fd就行

7.2 System V信号量

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/sem.h>

int semget(key_t key, int sems, int semflag);//创建一个新的信号量或获得已有信号量返回信号量表示符key_t key信号量的键值sems 创建的信号量数semflag 标志位,当为IPC_CRET时表示创建新的信号量,当为IPC_EXCL时表示若信号量已存在,则函数返回错误

int semctl(int semid, int semnum, int cmd, union semun arg);semnum 信号量标识符semnum 信号量编号(当使用信号量集时才有用,通常就取0)cmd 对信号量的操作IPC_STATIPC_SETVALIPC_GETVALIPC_RMIDunion semun{int val;struct semid_ids *buf;unsigned short *array;};

int semop(int semid, struct sembuf *sops, size_t nsops);struct sembuf{short sem_num; //通常取0short sem_op; //P操作为-1,V操作为+1short sem_flag; //通常设置为SEM_UNDO};

7.3 POSIX有名信号量

sem_open();sem_wait(); //P操作sem_post(); //V操作

7.4 共享内存

7.4.1 POSIX共享内存API

int shmget(key_t key, int size, int shmflg);char *shmat(int shmid, const void *shmaddr, int shmflg);//映射shmid:要映射的共享内存标识符shmaddr:将共享内存映射到指定地址shmflg:SHM_RDONLY:共享内存只读默认0:共享内存可读可写int shmdt(cosnt void *shmaddr);//解除映射int shmctl(int shemid, int cmd, struct shmid_ds *buf);shmid共享内存标识符cmd表示对共享内存执行的相关命令IPC_STAT 得到共享内存的状态IPC_SET 改变共享内存的状态IPC_RMID 删除该共享内存

7.4.2 mmap共享内存机制

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);//映射int munmap(void *addr, size_t len);//解除映射关系int msync(void *addr, size_t len, int flags);

7.5 消息队列

int msgget(key_t key, int msgflg);int msgsnd(int msgid, struct msgbuf *msgp, int msgsz, int msgflg);//msgid消息队列号,msgp存放信息的地址指针,msgsz消息文本的大小(不包含消息类型),msgflg可以设置为0或IPC_NOWAIT(当设置为IPC_NOWAIT的时候,如果消息队列已满,则不会被挂起等待,而是立即返回)int msgrcv(int msgid, struct msgbuf *msgp, int msgsz, long mtype, int msgflg);//msgid消息队列号,msgp存放读到的内容的地址指针,msgsz消息文本的大小(不包含消息类型),mtype希望从消息队列中读取到的消息类型,msgflg可以为0也可以为IPC_NOWAIT(当为IPC_NOWAIT时,如果消息队列中没有信息,则不挂起而是立即返回)struct msgbuf {long mtype; //消息类型char text[512]; //消息文本};所以两个函数的msgsz可以写成sizeof(struct msgbuf) - sizeof(long)int msgctl(int msgid, int cmd, struct msgqid_ds *buf);cmd:IPC_STAT 获取详细信息IPC_RMID 删除消息队列IPC_SET 设置消息队列的信息

8 线程

8.1 线程的相关操作

8.1.1 线程创建

#include<pthread.h>int pthread_create(pthread_t *pid, const pthread_attr_t *path_arrt, void *(*start_rtn)(void), void *arg);tid:要创建的线程的线程id指针path_arrt:所以创建线程的属性,通常为NULL即可start_rtn:函数指针类型,指向创建的线程所要执行的代码arg:传递给线程的参数

void *create(void *arg){//代码}

8.1.2 线程终止

void pthread_exit(void *ret_val);ret_val是线程终止时的返回值指针,该值返回给pthread_join

void pthread_cleanup_push(void (*rtn)(void *), void *arg);void pthread_cleanup_pop(int execute);//execute为0表示禁止执行指定函数,非0表示执行指定函数//在线程运行的时候将函数加入栈中//在线程以pthread_exit()结束后运行pop函数,系统会在线程结束前运行栈中的函数//这个功能可以用来回收资源

8.1.3 线程挂起

int pthread_join(pthread_t thread, void **thread_return);//作用:挂起当前线程,等待线程号为thread的线程结束,thread_return为指向thread线程返回值的指针

8.1.4 获得自身线程号

pthread_t thread = pthread_self();

8.2 互斥量

特点

原子性:如果一个线程锁定了一个互斥量,则临界区内的操作要么全部完成,要么一个也不执行唯一性:如果一个线程锁定了一个互斥量,那么在它解除锁定之前,没有其他线程可以锁定这个互斥量非繁忙等待:如果一个线程锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程被挂起

声明

pthread_mutex_t mutex;

初始化

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr *mutex_attr);

加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);//如果互斥量已经被加锁,则线程挂起等待

解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

判断是否加锁

int pthread_mutex_trylock(pthread_mutex_t *mutex);//如果互斥量已经被加锁,则返回EBUSY错误

销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

8.3 信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);int sem_wait(sem_t *sem);int sem_post(sem_t *sem);int sem_destroy(sem_t *sem);

8.4 条件变量

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);//作用:解锁mutex,使当前进程阻塞,直到收到一个解除阻塞的信号之后,再将mutex上锁,然后进行后续操作。int pthread_cond_signal(pthread_cond_t *cond);//释放被条件变量cond阻塞的一个线程int pthread_cond_destroy(pthread_cond_t *cond);//销毁条件变量cond

其他

命令行传参的方法

#include<stdio.h>int main(int argc, char *argv[]){/*argc是参数的数量(包括./test)argv[]中存储的是各参数。如命令行运行 ./test abc defargv[0]是./testargv[1]是abcargv[2]是def*/}

strcpy

strcpy(char *dest, char *src);//注意顺序

如果觉得《【北航软院】系统编程学习笔记》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。