【北航软院】系统编程学习笔记
当时复习了整整一周,可以说是写得最认真的笔记了,结果发现才两学分(然后没空复习数据库了)
文章目录
- 1.Linux命令基础
- 1.1 文件
- 1.2 路径
- 1.3 Linux命令
- 目录操作命令
- 文件操作命令
- 文件备份和压缩命令
- 1.4 VI编辑器
- 1.5 Linux命令行命令总结
- 1.6 POSIX标准和LSB标准
- 2.GCC/GDB/Makefile
- 2.1 GCC
- 2.2 GDB调试器
- 2.3 Make工具与Makefile
- 2.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 shebang
- 3.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 标准文件IO
- 4.6 错误处理函数
- 4.7 管道和重定向
- 4.7.1 重定向
- 例题:重定向代码分析
- 5 Linux进程管理
- 5.1 进程的概念
- 5.2 进程的创建和命令执行
- 5.2.1 fork
- 5.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 睡眠函数 sleep
- 6.8.2 定时函数 alarm
- 6.8.3 计时器 itimerval + setitimer
- 7 进程间通信
- 7.1 管道
- 7.1.1 无名管道
- 7.1.2 命名管道
- 7.2 System V信号量
- 7.3 POSIX有名信号量
- 7.4 共享内存
- 7.4.1 POSIX共享内存API
- 7.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编辑器
启动:在终端输入命令vi
或vim
即可启动VI编辑器。
三种状态:命令模式、插入模式和可视模式。
命令模式:可以在VI编辑器下方输入命令,如保存、查找和替换。
插入模式:对文本内容进行插入、删除、添加操作。
可视模式(底行模式):提供了友好的选取文本范围并显示高亮的功能。
切换:
用 vi 打开文件后默认进入命令模式。在命令模式输入操作符i
后进入插入模式,输入esc
退出插入模式,返回命令模式;在命令模式下输入:
或/
进入底行模式,输入esc
退出底行模式,返回命令模式。
命令模式下的命令:
:w 将文件内容保存到vi命令所指定的文件中
:w filename 将文件另存为filename
:wq! 将文件内容保存到vi命令所指定的文件中然后退出vi
:wq! filename 以filename为文件名保存后退出
:q! 不保存直接退出
:x 保存并退出,功能和:wq!相同
:q 退出命令,如果文件内容有更改,VI会提示需要保存才能退出
j
k
h
l
分别代表向下、上、左、右移动
通过插入命令可以由命令模式进入插入模式,如i
a
I
A
o
都可以。
在命令模式下按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 www.baidu.com #检查网络是否连通#软件安装命令
$ 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.o
将test.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.o
gcc -c mytool2.c -o mytool2.o
ar 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.o
gcc -c -fPIC mytool1.c -o mytool1.o
gcc -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 init
,grep
的作用查找字符串中是否包含某字符串,这个管道命令的意思是查找/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/bash
echo '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.sh
test.sh #或 ./test.sh
3.5.1 特殊字符
转义字符/
:当希望使用特殊字符的字面义时
单引号''
:单引号所包含的所有字符保持字面意义(单引号不能嵌套)
双引号""
:使除了$ / `之外的其他所有字符保持字面义,意思是:
$ 和`两个字符保持特殊含义
而转义字符/只有后面跟的是$ `` " 或换行符的时候才有转义的特殊作用
string=hello
echo $string #输出hello
echo '$string' #输出$string
echo "$string" #输出hello
echo "\string" #输出\string
echo "\$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
未定义或为null
,word
被写至标准出错(默认情况下在屏幕显示word
信息)然后退出,否则用parameter
置换变量的值。变量=${parameter:+word}
:如果parameter
未定义或为null
,则不进行替换,即变量值也为null
,否则用word
置换变量的值。
shell变量的算术扩展
$((…))将需要计算的表达式包含在其中,用计算结果替换
expr 表达式 计算并显示表达式的值
3.5.3 shell条件表达式
格式:
if [ $i -lt 10 ]; then
fi
test 表达式 或者直接使用 [表达式]判断条件表达式真假
字符串操作:
-z String 若String长度为0,表达式为真
-n String 若String长度不为0,表达式为真
String1 = String2 若String1与String2相等,表达式为真
String1 != String2 若String1与String2不相等,表达式为真
逻辑操作:
!expr
expr1 -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 $string
expr "$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位置赋值为value
array=(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/bash trap "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,分别表示开始、当前、末尾执行成功返回移动之前的指针位置,失败返回-1
7. 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);
fseek
ftell
fwrite
fclose
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 = 3
fd2 = 4
fd3 = 5
fd4 = 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
表示可执行程序的名称或路径。
execl
和execv
的path要求用绝对路径
execlp
和execvp
的path可以只写文件名,由系统自动用$path
环境变量补全路径
l
和v
函数的传参方式不一样,l
表示list
,即参数是逐个列举的,v
表示vertor
,即将所有参数整体构造为指针数组传递。
l
和v
的第一个参数都必须是可执行文件的名字。
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+C
是SIGINT
,CTRL+/
是SIGQUIT
,还有非法段访问信号、浮点数溢出信号等。
信号的状态:
递送delivery
:进程对信号采取动作(执行信号处理函数或忽略)时称为递送;
未决pending
:信号产生后和递送前的时间间隔为未决;
信号递送阻塞block
:进程可指定对某个信号采用递送阻塞,如果此信号的处理方式为默认或捕捉,该信号就会处于未决态,直到进程解除对该信号的递送阻塞 或 信号处理方式变为忽略。如果该信号的处理方式是忽略,那么该信号永远不会处于递送或未决态,即被丢弃。
6.2 信号的分类
6.2.1 可靠与不可靠
可靠信号:多个信号同时出现时会排队,不会丢失
不可靠信号:多个信号同时产生时可能会丢失信号
6.2.2 实时与非实时
实时信号即可靠信号
非实时信号即不可靠信号
6.3 信号的处理
1.默认:信号的默认处理是终止进程
2.忽略:可以通过signal(SIGINT, SIG_IGN);
函数使进程忽略某信号
3.捕捉并处理:除了SIGKILL
和SIGSTOP
信号以外,其他信号都可以通过信号处理函数捕捉并处理。常见的信号处理机制有signal
和sigaction
两种。
6.3.1 signal
信号处理机制
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.2 sigaction
信号处理机制
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_DFL
、SIG_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
实现的,因此sleep
和alarm
最后不要混用,以免造成混乱。
6.8.2 定时函数 alarm
alarm(t); //定时t
alarm(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_RMID
union 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);
//注意顺序
【北航软院】系统编程学习笔记相关推荐
- 5w字总结 Unix系统编程学习笔记(面试向)(Unix环境高级编程/Unix环境程序设计)
文章目录 一.计算 C语言的数据表示与处理 计算 C语言的基本运算操作 内存表和符号表 类型转换 函数类型的分析 指令 复合指令 句法 函数 函数激活(Activation Record) 函数激活定 ...
- Linux系统编程学习笔记(九)进程间通信IPC
进程间通信IPC: 我们以前介绍过进程控制原语,看到怎么创建多个进程.但是进程之间交互信息的方式只介绍了通过fork或者exec继承父进程的打开文件或者通过文件系统. 经典的进程通信方式有:管道.FI ...
- linux线程并不真正并行,Linux系统编程学习札记(十二)线程1
Linux系统编程学习笔记(十二)线程1 线程1: 线程和进程类似,但是线程之间能够共享更多的信息.一个进程中的所有线程可以共享进程文件描述符和内存. 有了多线程控制,我们可以把我们的程序设计成为在一 ...
- 【北航软院+保研复习】计算机网络复习笔记
基于王道计网 1.0版本 北航软院大三上计网课程复习笔记 2.0版本 保研复习笔记 文章目录 第一章 1.1 计算机网络概述 1.1.1 计算机网络的概念 1.1.2 计算机网络的组成 1.1.4 计 ...
- 北航软院非全研究生备考指南
本文首发于我的个人博客:「程序员充电站」 文章链接:传送门 一份关于「北航软院非全日制研究生」,「电子信息工程专业」的备考指南. 我的背景:2020 级,北航软院非全日制研究生,电子信息工程专业,相关 ...
- 【Linux系统编程学习】Linux进程控制原语(fork、exec函数族、wait)
此为牛客Linux C++和黑马Linux系统编程课程笔记. 1. fork函数 1.1 fork创建单个子进程 #include<unistd.h> pid_t fork(void); ...
- Linux与C++11多线程编程(学习笔记)
多线程编程与资源同步 在Windows下,主线程退出后,子线程也会被关闭; 在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程 3.2.1创建线程 Linux 线程的创建 #inc ...
- 软考信息安全工程师学习笔记汇总
软考信息安全工程师学习笔记汇总 https://www.moondream.cn/?p=178 2020年软考信息安全工程师备考学习资料包 1.<信息安全工程师教程>重点标记版 2.& ...
- WCF服务编程 学习笔记(1)
你或许可以使用某一技术实现某些功能,可以按着指定的要求,完成特定的功能,实现某一想要的效果,这表示你可以使用该技术,会使用该技术,但是我们不能停留在使用的层次上,还要了解它们的运行机制,可能有点深了, ...
最新文章
- TPC性能测试及发布
- 华中科大提出EAT-NAS方法:提升大规模神经模型搜索速度
- 专访阿里云域名与网站业务总经理宋瑛桥:域名未来将更加个性化、生态化和规范化...
- kubernetes1.8.4安装指南 -- 2. ssh免密登录
- 鸿蒙开发者有多少,鸿蒙开发者beta版本申请通过的过来人有几句话要说
- android 部分区域点击,Android编程实现ListView中item部分区域添加点击事件功能
- javaWeb:相关监听方法汇总
- 2. COM编程——什么是接口
- 64位驱动签名工具64Signer
- 还记得最初的九九乘法表吗?
- 《JeolOnSoftware》
- APP逆向案例之(一)过 app 更新提示
- 浅谈Windows XP系统漏洞的封堵
- 焦虑 程序员_我如何克服焦虑和沮丧来完成freeCodeCamp的前端开发程序
- -bash: lsb_release: 未找到命令
- 验证谷角猜想。日本数学家谷角静夫在研究自然数时发现了一个奇怪现象:对于任意一个自然数 n ,若 n 为偶数,则将其除以 2 ;若 n 为奇数,则将其乘以 3 ,然后再加 1。如此经过有限次运算后,总可
- 1.27BSC什么意思?
- a的b次方对p取模 a乘b对p取模 快速幂
- RocketMQ系列:搭建3m-noslave模式的rocketmq集群
- 小灯泡自媒体博客Spimes4.6收费typecho主题模板无加密无授权源码