Linux

基本操作

登录

login		# 登录
who			# 显示所有用户
whoami		# 显示当前登录的用户
man <命令>	# 显示命令帮助
exit		# 退出终端

常用操作

ed

$ ed              <- 激活 ed 命令 
a                 <- 告诉 ed 我要编辑新文件 
My name is Titan. <- 输入第一行内容 
And I love Perl very much. <- 输入第二行内容 
.                 <- 返回 ed 的命令行状态 
i                 <- 告诉 ed 我要在最后一行之前插入内容 
I am 24.          <- 将“I am 24.”插入“My name is Titan.”和“And I love Perl very much.”之间 
.                 <- 返回 ed 的命令行状态 
c                 <- 告诉 ed 我要替换最后一行输入内容 
I am 24 years old. <- 将“I am 24.”替换成“I am 24 years old.”(注意:这里替换的是最后输的内容) 
.                 <- 返回 ed 的命令行状态 
w readme.text     <- 将文件命名为“readme.text”并保存(注意:如果是编辑已经存在的文件,只需要敲入 w 即可) 
q                 <- 完全退出 ed 编辑器

ls

ls
ls -l		# 详细信息
ls -a		# 包括隐藏文件
ls –t  		# 按修改时间排序
ls-l

cat

cat			# 显示文件

mv

mv <源文件> <目的文件>		# 移动文件
# 如果源文件==目的文件,相当于重命名

cp

cp <源文件> <目的文件>		# 复制文件

rm

rm <文件...>		# 删除文件

wc

wc <文件名>
# 显示文件的:行数、词数、字节数

cmp

cmp <文件1> <文件2>
# 如果文件相同,则不显示信息。如果文件不同,则显示第一个不同的位置

diff

diff <文件1> <文件2>
# 显示两个文件不同的地方

文件目录

pwd

pwd			# 显示当前路径

cd

cd <目录名>	# 打开目录
cd ..		# 打开上一级目录

mkdir

mkdir <目录名>		# 创建子目录

rmdir

rmdir <目录名>		# 删除子目录

通配符

*			0个或多个字符
?			任何一个字符
[a-z]		a-z所有字符
[^a-z]		a-z之外的所有字符
[xyz]		xyz中任一字符
[^xyz]		除xyz任一字符
\			转义字符

ls *.c
ls a*[1-9].c
ls  ?.c
echo *
echo \*

重定向

>			# 标准输出重定向
2>			# 错误输出重定向
>>			# 标准追加重定向
2>>			# 错误追加重定向

<			# 输入重定向
<< 分界符		# 从标准输入设备(键盘)中读入,直到遇到分界符才停止(读入的数据不包括分界符),这里的分界符其实就是自定义的字符串

管道

<命令1> | <命令2>			# 前面命令的输出作为后面命令的输入

进程

who; whoami  		# 多个命令同时运行
<命令>&  				# 后台运行
ps  				# 显示当前运行的进程
kill <进程编号>		# 终止某个进程

环境

PS1='`whoami`:`pwd` >'	# m
$HOME
$PATH

执行多个命令

  • 分号:顺序地独立执行各条命令, 彼此之间不关心是否失败, 所有命令都会执行。

  • &&:顺序执行各条命令, 只有当前一个执行成功时候, 才执行后面的。

  • ||:顺序执行各条命令, 只有当前面一个执行失败的时候, 才执行后面的。

文件系统

文件权限

9个权限位,每3个为一组,分别代表文件所有者、同组用户和其他用户的权限。(见上面图片)

rwxrwxr-x

chmod <mode> <文件名...>

<mode> 权限设定字串,格式如下:
	[ugoa][+-=][rwxX]
	u 表示该文件的拥有者,g 表示同组者,o 表示其他以外的人,a 表示这三者皆是,不写默认为a。
	+ 表示增加权限、- 表示取消权限、= 表示唯一设定权限。
	r 表示可读取,w 表示可写入,x 表示可执行,X 表示只有当文件为目录文件,或者其他类型的用户有可执行权限时,才将文件权限设置可执行。
	举例:
		chmod o+w hello.c
		chmod a+x hello.c
		chmod a-x hello.c
		chmod a=r hello.c
		chmod ug+w hello.c
	
	八进制语法:将rwx分别对应三位二进制,如rw-对应110,即为6。
	举例:
		chmod 777 hello.c
		chmod 664 hello.c	# rw-rw-r--

目录文件权限

  • x: 表示具有进入目录的权限
  • r: 表示具有浏览目录下有哪些文件的权限,必须同时有x权限才可以
  • w:表示具有新增、删除、移动目录内容文件的权限,同时需要有x权限

文件编辑和流处理

ed

a 从当前行开始添加
.  结束添加状态   //必须在行首输入
d 删除当前行
w 存盘
q 退出ed编辑器

临时进入shell   !

显示命令 p
p   显示当前行
5   将当前行改为第5行并显示当前行
m,np  显示m到n行
.代表当前行,$代表最后一行
可使用相对行,如 .,.+3p   $-5,$p   .加偏移量时可以省略。

查找
/模式/ 查找下一个匹配模式的行,遇到尾行则从首行开始
?模式? 查找上一个匹配模式的行,遇到首行则从尾行开始
查找会改变当前行
查找返回的是行号,可使用。
1,/main/p        显示1到第一次出现main的行
/main/-1,$p     显示第一次出现main的行的上一行到最后一行
5,?int?d           删除5到最后一次出现int的行

插入、删除、撤销
na 从n行后添加
ni  从n行前插入。i、a都以行首.来结束
m,nd 删除m至n行
u 撤销所做的编辑

替换
s/old/new/ 把当前行中第一个old替换为new
s/old/new/g 把当前行中每一个old都替换为new
1,$s/old/new/g 把文件中所有的old都替换为new
速记符&
1,$s/big/very &/g 把文件中的big都替换为very big
s/and/\&/ 把and改为&

ed的正则表达式
c  普通字符,与自己匹配,如a、b等
\c 取消字符c的特殊意义,如\&
^  在模式起始位置时,代表行首。如^a表示行首为a
$  在模式结束位置时,代表行尾。
.  匹配任意单个字符。
[…] 匹配[]中任意单个字符,如[1ac],[a-z], [1-9]
[^…] 匹配不在…中的任意单个字符
r*,零个或多个重复的r,r为单个字符。如a*, [ab]*, .*
& 仅用于替换命令s的右边,代表第一个模式

模式举例
/^$/ 空行
/./    非空行
/^/   任意行
/thing/  包含字符串thing的行
/^thing/  以thing开始的行
/thing$/   以thing结尾的行
/^thing$/ 内容为thing的行
/thing.$/  以thing加任意符号结尾的行
/thing\.$/  以thing.结尾的行
/\/thing\//  包含/thing/的行
/[tT]hing/ 包含thing或Thing的行
/thing[0-9]/
/thing[^0-9]/
/thing[0-9][^0-9]/
/thing1.*thing2/
/^thing1.*thing2$/

全局命令
命令格式:m,ng/re/cmd,含义是从m行到n行中对于匹配re模式的行执行命令cmd. 如果作用范围是全文件(1,$),m、n可以省略。
g/…/p 显示所有包含…的行
g/…/d 删除所有包含…的行
g/…/s//rep1/ 对于包含…的行,将其中第一个…替换成rep1
g/…/s//rep1/g  把所有的…替换成rep1
g/…/s/pat/rep1/ 对于包含…的行,将其中第一个pat替换成rep1
v/^$/p 打印所有非空行

移动、复制
命令格式:m,nmd  m到n行移到d行之后。
        m,ntd  m到n行拷贝到d行之后
		g/^/m0  逆序

sed

语法:sed [-opt] 'ed command1;ed command2' file
sed从输入文件中依次读取每一行,按指定的ed命令处理,将结果送至标准输出,但不改变file本身。

who | sed 's/ .*//' 		# 显示当前登录用户,只显示用户名。
sed '10q' file     			# 显示file文件的前10行
sed '/pattern/d' file  		# 不显示包含pattern的行。
sed -n '/pattern/p' file 	# 仅显示包含pattern的行。sed默认显示每一行,-n选项关闭自动显示功能。
ls -l | sed -n '/^d/p' 	# 列出当前目录下的子目录

grep

语法:grep 'ed pattern' file

grep '^void' sig.c  				# 显示sig.c文件中以void开头的行
ls -l | grep '^d'   				# 列出当前目录下的子目录
ls -l | grep '^d' | sed 's/.* //' 	# 列出当前子目录名

awk

基本语法

awk 'pattern{action} pattern{action}' <file>

awk '/pattern/' file 	# 显示匹配模式的每一行,功能同grep
awk '{print}'  			# 显示每一行,功能同cat
awk  '//{print}/main/{print"----------------------------"}'  sig.c

常量

NR			# 当前行数(记录数)
NF			# 一行的字段数(单词数)
$1$2... 	# 第n个字段
$0			# 整行

awk '{print NR,NF,$0}' file
ls -l | awk '/^d/{print}' 				# 列出当前目录下的子目录
ls -l | awk '{print NR,":"$1,$8}'		# (,相当于输出一个空格)
awk '{printf("%d: %s\n",NR,$0)}' file

表达式

< <= > >= != ==		# 关系运算符
~ !~				# 匹配正则表达式和不匹配正则表达式
+ -	* / %			# 加,减,乘,除与求余
|| &&				# 逻辑或,逻辑与

ls -l | awk '$5~/.....*/'  		# 列出长度超过999字节的文件
ls -l | awk '$5=="4096"'  		# 列出长度为4096字节的文件
ls -l | awk 'length($9)>=5'  	# 列出文件名长度大于等于5的文件

特殊pattern

awk 'BEGIN{初始化动作}'
awk 'END{结束动作}'

变量和运算

awk '{
		nw += NF;
		nc +=length($0)+1;
	}
	END{
		print NR, nw,nc;
	}'  file		# 与wc命令的功能完全一样
	
awk 'NF>0{
   if ($1==lastword)
      printf("double %s, line %d:\n%s\n", $1,NR,$0);
   for (i=2; i<=NF; i++)
       if ($i==$(i-1))
          printf("double %s, line %d:\n%s\n", $i,NR,$0);
    lastword=$NF;
} ' file		# 寻找文件中相同的相邻单词

流程控制

ifforwhile都与C语言相同
break:跳出循环
continue:下一个循环
next:下一条记录,回到awk程序开始
exit:跳转至END模式或结束

内部变量

FILENAME	# 当前输入文件名
RS    		# 输入记录的分割符(默认为换行符)
FS    		# 输入字段的分隔符(默认为空格、制表符)
NF    		# 当前记录的字段数
NR    		# 当前记录数
OFS   		# 输出字段的分隔符(默认为空格)
ORS  		# 输出记录的分隔符(默认为换行)

内部函数

cos(expr) 		# 求余弦
exp(expr)  		# 求自然指数
index(s1,s2)    # 是否字符串s2位于s1中
int(expr)       # 取整
length(s)   	# 求字符串长度
log(expr)   	# 求自然对数
sin(expr)    	# 求正玄
split(s, a, c) 	# 按分隔符c将s放入a[1],a[2],…中
substr(s,m,n) 	# 求s的子串,从第m个字符开始,共n个字符。

数组

awk '{line[NR] = $0}
     END{
         for (i=1; i<=NR; i++) print line[i]
     }' file

shell编程

参数重置与移动

# set命令可以重置除$0外所有参数。

set b1 b2
echo $0 $1 $2 $3 $4		# $1 $2被重置为b1 b2

# shift命令可以左移除$0以外的位置参数。

shift
echo $0 $1 $2 $3 $4		# $1变成原来的$2,以此类推
shift 2
echo $0 $1 $2 $3 $4		# 承接上一个shift,再左移两位,$1变成原来的$4,以此类推

特殊符号含义

*     			# 匹配文件名中任意字符串
?     			# 匹配文件名中任意单个字符
[ccc]			# 匹配文件名中单个ccc中的字符。ccc可以指定范围,如0-9,a-z等
;  				# 命令结束符。p1;p2,先执行p1,再执行p2
&  				# 后台命令结束符。不等命令结束,立即接受新的命令
`…`  			# 执行命令…,用执行后的标准输出代替…   例如$ echo `date`
(…)   			# 在子shell里运行括号里的命令
$1, $2, …   	# 位置参数
$var			# 引用变量var的值
${var} 			# 同上,在可能引起歧义时,使用{}将变量名括起来。
'…'  			# 单引号,对…中的特殊字符不作解释
"…" 			# 双引号,对…中的特殊字符仅解释$\``
var=value  		# 对变量var赋值
P1 && p2   		# 运行p1,若成功,运行p2
P1 || p2     	# 运行p1,若不成功,运行p2

$#				# 参数个数
$*   			# 命令行参数集合
$@  			# 命令行参数集合  //与$*有细微差别
$?    			# 最后一条命令的返回值
$$  			# 当前shell的进程号
$!   			# 最后一个后台命令的进程号
$HOME  $PATH  $PS1  $PS2

test

常用于ifwhileuntil命令中的条件判断

数值测试:
-eq				# 等于则为真
-ne				# 不等于则为真
-gt				# 大于则为真
-ge				# 大于等于则为真
-lt				# 小于则为真
-le				# 小于等于则为真

字符串测试:
=				# 等于则为真
!=				# 不相等则为真
-z 				# 字符串	字符串的长度为零则为真
-n 				# 字符串	字符串的长度不为零则为真

文件测试:
-e 文件名		# 如果文件存在则为真
-r 文件名		# 如果文件存在且可读则为真
-w 文件名		# 如果文件存在且可写则为真
-x 文件名		# 如果文件存在且可执行则为真
-s 文件名		# 如果文件存在且至少有一个字符则为真
-d 文件名		# 如果文件存在且为目录则为真
-f 文件名		# 如果文件存在且为普通文件则为真
-c 文件名		# 如果文件存在且为字符型特殊文件则为真
-b 文件名		# 如果文件存在且为块特殊文件则为真

其他:
–a 			# 与
-o 			# 或
!			# 非
test f1 –nt f2  #文件f1比文件f2新
test f1 –ot f2  #文件f1比文件f2旧

if

if list			# list:命令序列
then			# 必须换行
	list
[elif list
then
	list]
[else list]
fi

# 例1
if gcc ./hello.c
then
   echo good program!
else
   echo bad program!
fi
# 例2:判断参数个数
if test $# -eq 5
then
   echo there are 5 argus
elif test $# -gt 5
then
   echo more than 5 argus
else
   echo less than 5 argus
fi

case

casein
模式1)
    command1
    command2
    ...
    commandN
    ;;
模式2)
    command1
    command2
    ...
    commandN
    ;;
esac

例:
case $# in
5)
   echo there are 5 argus;;
[0-4])
   echo less than 5 argus;;
*)
   echo more than 5 argus
esac

for

for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
donefor var in item1 item2 ... itemN; do command1; command2… done;
或
for ((e1;e2;e3))	# 仅bash
do
   list
done

例1:
for i in $*
do
  echo $i
done
例2:
j=0
for i in $*
do
  a[$j]=$i
  let j=$j+1
done
for ((i=$#-1; i>=0; i--))
do
  echo ${a[i]}
done

while

while condition
do
    command
done

例:
int=1
while(( $int<=5 ))
do
    echo $int
    let "int++"
done

until

# until 循环执行一系列命令直至条件为 true 时停止。until 循环与 while 循环在处理方式上刚好相反。
until condition
do
    command
done

系统调用

文件IO

文件描述符

一个非负整数,一个结构数组的下标,进程打开的文件表项的下标。open、creat函数会返回一个文件描述符。

文件描述符0、1、2默认打开,分别对应于标准输入(键盘)、标准输出(显示器)、标准错误输出(显示器)文件。在<unistd.h>中定义为STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO

open

#include <fcntl.h>
int open(const char *pathname, int oflag, [mode_t mode])
// 返回值:若成功,返回非负整数,即文件描述符。一定是当前“进程文件描述符表”中最小未使用的描述符。出错返回-1。
// pathname,常量,文件名,绝对路径或相对路径均可。
// oflag,打开方式选项。O_RDONLY、O_WRONLY、O_RDWR三者必须选其一。O_CREAT、O_APPEND、O_TRUNC等任意选择。多个选项进行“或”运算构成oflag选项。
/*
	O_RDONLY	只读模式
	O_WRONLY 	只写模式
	O_RDWR		读写模式

	O_APPEND	每次写操作都写入文件的末尾
	O_CREAT		如果指定文件不存在,则创建这个文件
	O_EXCL		如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
	O_TRUNC		如果文件存在,并且以只写/读写方式打开,则清空文件全部内容(即将其长度截短为0)
	O_NOCTTY	如果路径名指向终端设备,不要把这个设备用作控制终端。
	O_NONBLOCK	如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O
*/
// mode,仅当oflag具有O_CREAT选项时,需要此参数,用于指定新建文件的访问权限。

creat

#include <fcntl.h>
int creat(const char *pathname, mode_t mode)
// 返回值:若成功,返回非负整数,即文件描述符。出错返回-1
// pathname,常量,文件名,绝对路径或相对路径均可。
// mode,指定新建文件的访问权限。
// 若原有文件存在,则原有文件的属性和内容将会被覆盖。等价于:
open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode)
// 语义上creat简单,涉及原子操作,应当使用open。

close

#include <unistd.h>
int close(int filedes)
// 返回值:若成功,返回0,出错返回-1
// filedes,文件描述符
// 进程终止时,内核自动关闭其打开的所有文件

lseek

// 移动文件“读写指针”(或称“文件偏移量”)。读写操作会自动移动文件读写指针。
#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence)
// off_t  与系统有关,通常是长整型
// filedes 文件描述符
// whence 移动偏移量的方式。SEEK_SET/0:绝对方式;SEEK_CUR/1:相对方式;SEEK_END/2:相对文件尾部
// offset  移动的距离,非绝对方式移动时,可以为负。
// 返回值:新的文件偏移量。

read

// 从已打开的文件中读取数据,自动移动文件读写指针。
#include <unistd.h>
ssize_t read(int filedes, void *buff, size_t nbytes)
// ssize_t  通常是整型,size_t通常是无符号整型。
// filedes 文件描述符
// buff  存放数据的缓冲区
// nbytes  需要读取的字节数
// 返回值:实际读取的字节数。正常读取时,返回值等于nbytes;遇到文件结束时,小于nbytes;出错返回-1

write

// 将数据写入已打开的文件,自动移动文件读写指针。
#include <unistd.h>
ssize_t write(int filedes, void *buff, size_t nbytes)
// ssize_t  通常是整型,size_t通常是无符号整型。
// filedes 文件描述符
// buff  存放数据的缓冲区
// nbytes  需要写入的字节数
// 返回值:实际写入的字节数。正常写入时,返回值等于nbytes;磁盘空间满时,小于nbytes(这种情况也可以认为是出错);出错返回-1

dup2

#include <unistd.h>
int dup2(int filedes1, int filedes2)
// 将文件描述符filedes1的表项复制给filedes2的表项。如果filedes2已经打开,则先将其关闭。
// 返回值:正常返回filedes2,出错返回-1
文件IO

原子操作

// 例1  多个进程添写同一个日志文件
// 有问题的代码:
lseek(fd, 0L, 2)
write(fd, buf, nbytes)	// 如果从这两条代码中间插入其他代码,就会出现覆盖的情况
// 正确的做法:
open时,使用O_APPEND选项
write时,不需要调用lseek,直接写。
    
//例2  多个进程创建可能重名的临时文件
// 有问题的代码:
if (open(tmpfile, O_WRONLY)<0)
     creat(tmpfile, mode)
// 正确的做法:
open(tmpfile, O_CREAT|O_WRONLY|O_TRUNC, mode)
最好的做法是保证没有重名的临时文件。

文件属性和目录

文件结构

struct stat
{
	mode _t		st_mode;	//文件类型和权限
	ino_t		st_ino;		//i节点号
  	dev_t		st_dev;		//文件系统设备号(磁盘和分区)
    dev_t 		st_rdev;    //设备文件的设备号
    nlink_t  	st_nlink; 	//链接数
    uid_t      	st_uid;  	//文件所有者的用户ID
    gid_t   	st_gid; 	//文件所有者的组ID
    off_t  		st_size;  	//文件长度(字节数),普通文件
    time_t   	st_atime;  	//最后一次访问的时间
    time_t 		st_mtime; 	//最后一次修改文件内容的时间
    time_t   	st_ctime; 	//最后一次修改文件属性的时间
}

// 相关函数
#include <sys/stat.h>
int stat(const char *pathname, struct stat *buf)
int fstat(int filedes, struct stat *buf)

文件类型

类型:普通文件、目录文件、字符设备文件、块设备文件、 FIFO、符号链接、套接字

// 判断文件类型
S_ISREG(mode_t mode)   	//是否普通文件
S_ISDIR(mode_t mode)    //是否目录文件
S_ISCHR(mode_t mode)   	//是否字符设备文件
S_ISBLK(mode_t mode)   	//是否块设备文件
S_ISFIFO(mode_t mode)  	//是否管道文件
S_ISLNK(mode_t mode)   	//是否符号链接
S_SOCK(mode_t mode)   	//是否套接字

文件的权限

// 9个普通权限位
S_IRUSR , S_IWUSR , S_IXUSR   //用户权限位
S_IRGRP,  S_IWGRP, 	S_IXGRP   //用户组权限位
S_IROTH,  S_IWOTH,  S_IXOTH   //其他用户权限位
// 3个特殊权限位(针对可执行文件)
S_ISUID  执行时设置有效用户ID,如passwd命令文件
S_ISGID  执行时设置有效用户组ID
S_SVTX   第一次执行时,保存正文,即常驻内存。
// 相关函数
int chmod(const char *pathname, mode_t mode)
int fchmod(int filedes, mode_t mode)

常用函数

// 普通文件操作函数
# include <unistd.h>
int link(const char *existingpath, const char *newpath)  //创建一个新的目录项newpath,指向一个现有的文件existingpath。
int unlink(const char *pathname)  //删除一个目录项,对应文件的链接数减1。
#include <stdio.h>
int remove(const char *pathname)  //删除一个文件或目录的链接。
int rename(const char *oldname, const char *newname)  //文件或目录更名

// 目录文件操作函数
# include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode)  //创建一个目录
# include <unistd.h>
int rmdir(const char *pathname)  //删除一个空目录。
    
// 目录文件读函数(只有内核才可以写)
#include <dirent.h>
DIR *opendir(const char *pathname)
struct dirent *readdir(DIR *dp)
int closedir(DIR *dp)
void seekdir(DIR *dp, long loc)

//位置函数
//改变进程的当前工作目录(cd命令)
# include <unistd.h>
int chdir(const char *pathname)
int fchdir(int filedes)
//获取当前工作目录(pwd命令)
#include <unistd.h>
char *getcwd(char *buff, size_t size)

进程控制

进程ID(进程号)

#include <unistd.h>
pid_t getpid(void)   // 获取调用者的进程ID
pid_t getppid(void)  // 获取调用者的父进程ID。

创建进程(fork)

#include <unistd.h>
pid_t fork(void)
// 功能:创建一个新的进程,新进程是旧进程的副本。旧进程叫父进程,新进程叫子进程。
// 返回值:fork函数调用一次,返回两次。在父进程中返回子进程的ID,在子进程中返回0,出错返回-1
    
// 以下是一个例子:
pid = fork();  	//创建一个子进程
if(pid < 0)		//判断子进程是否创建成功
{
    printf("子进程创建失败,很遗憾!\n");
    exit(1);
}
//走到这里,fork()成功。现在,父进程和子进程同时开始运行了 
//执行后续代码的可能是父进程,也可能是子进程
if(pid == 0)
{
    //子进程,因为子进程的fork()返回值会是0;
    //这里专门针对子进程的处理代码
    ......
}
else
{
    //这里就是父进程,因为父进程的fork()返回值会 > 0(实际返回的是子进id程)
    //这是专门针对父进程的处理代码
    ......
}

执行程序(exec)

// 父进程fork一个子进程后,子进程往往需要调用一个exec函数来运行一个新程序。
// exec函数用一个新程序替换调用进程原有的代码、数据、堆栈等,新程序从头开始执行。exec不产生新的进程,所有调用前后的进程号不发生变化。
// exec函数:
#include <unistd.h>
int execl(const char *pathname, const char *arg, 参数列表, NULL)
int execv(const char *pathname, char *const argv[])
int execle(const char *pathname, const char *arg, 参数列表, NULL, char *const envp[])
int execve(const char *pathname, char *const argv[], char *const envp[])
int execlp(const char *filename, const char *arg0, 参数列表, NULL)
int execvp(const char *filename, char *const argv[])
/*
说明:
1) execl和execlp比较直观,类似命令行输入,推荐使用。
2)execve是系统调用,其他是库函数。
3)返回值:正常不返回,出错返回-1
*/
    
// 例子:
char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};
char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execv("/bin/ps", ps_argv);
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
execve("/bin/ps", ps_argv, ps_envp);
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execvp("ps", ps_argv);

文件共享

fork之后,所有父进程打开的文件描述符都被复制到子进程中。父子进程相同的文件描述符指向相同的内核文件表,具有相同的文件偏移量。如果父子进程同时操作相同的文件,则需要同步机制,否则会产生混乱。一般情况下,应该尽量回避父子进程同时操作相同文件的情况。

  • 父进程等待子进程结束后再运行
  • 父子进程运行不同的程序段,使用不同的文件。

进程结束

  • 正常结束:main函数执行完;main函数中执行return;任意位置执行exit、_exit、_Exit
  • 非正常结束:收到某个信号而结束
  • 无论进程如何结束,都会执行内核中的一段代码:关闭所有打开文件,释放占用的内存。
  • 进程结束时,内核会保存其终止状态,直到该进程的父进程取走其状态。
  • 父进程结束,其子进程成为孤儿进程,由进程1(init进程)领养。
  • 子进程先于父进程结束,如果父进程未取走其状态,则该进程仍然占有一定的内核资源,成为“僵死进程”。
  • 由于僵死进程占用资源,因此当一个长期运行的服务类程序调用fork后,应处理僵死进程。

wait

#include <sys/wait.h>
pid_t   wait(int *status)
/*
	进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
	参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,pid = wait(NULL);
	如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
*/
// 例子:
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
        pid_t pc,pr;
        pc=fork();
        if(pc<0) /* 如果出错 */
                printf("error ocurred!\n");
        else if(pc==0){ /* 如果是子进程 */
                printf("This is child process with pid of %d\n",getpid());
                sleep(5); /* 睡眠5秒钟 */
        }
        else{ /* 如果是父进程 */
                pr=wait(NULL); /* 在这里等待 */
                printf("I catched a child process with pid of %d\n",pr);
        }
       exit(0);
}

// 其他函数
pid_t   waitpid(pid_t pid, int *stat, int options)
waitid、wait3、wait4等

进程间通信(IPC)

进程间通信包括:半双工管道、半双工命名管道(FIFO)、全双工管道、全双工命名管道、消息队列、信号量、共享存储、套接字、STREAMS。

主机内通信常用半双工管道,网络通信常用套接字。

半双工管道

所有UNIX系统都提供的一种通信方式。通信方向是双向的,但只能选择其中一种。只能在具有公共祖先的进程之间使用,通常是父子进程之间。

#include <unistd.h>
int pipe(int filedes[2])
// 创建一个管道,若成功返回0,不成功返回-1;
// 由参数filesdes返回两个文件描述符: filedes[0]、filesdes[1],filedes[0]为读而打开,filedes[1]为写而打开。
// (单个进程)创建管道之后,将数据写入fd[1],从fd[0]中读出。
// fork之后,选择通信方向,如果父进程写、子进程读,则父进程close fd[0],子进程close fd[1];反之,子进程关闭fd[0],父进程关闭fd[1]。
// 规则:(1)写端关闭时,read函数返回0,表示文件结束;(2)读端关闭时,write函数返回-1,并且出现SIGPIPE异常(信号)。
// 常量PIPE_BUF规定了内核管道缓冲区大小,每次write的字节数需小于PIPE_BUF
    
// 例1(父进程写,子进程读):
int fd[2]; char line[MAXLINE];
pipe(fd);
if (pid = fork() > 0)	//父进程
{
	close(fd[0]);
	write(fd[1], "hello world!\n", 12);
}
else if (pid == 0)	//子进程
{   
	close(fd[1]);
	read(fd[0], line, MAXLINE);
}

// 例2(用管道连接父子进程的标准输出和输入):
if (pid = fork() > 0)	//父进程
{   
	close(fd[0]);
	dup2(fd[1], 1);
}
else if (pid=0)		//子进程
{
	close(fd[1]);
	dup2(fd[0],0);
	execlp(……)
}
......		//父进程代码
pipe

命名管道(FIFO)

# 命名管道是一种文件
$ mkfifo f1
$ ls -l f1
$ cat < f1		# 在终端1中接收管道内容
$ ls -l > f1	# 在终端2中向管道输入,此时终端1会输出结果
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)

信号

信号是一种异步事件,通常是非正常情况,如:人为终止进程、除数为0、非法内存访问等。所谓“异步”,是指信号的出现是随机的。

信号的产生

  • 人为按下中断键 Ctrl_C
  • 用户使用kill命令
  • 进程调用kill函数
  • 程序运行错误,除数为0、非法内存访问
  • 软件产生的信号:闹钟超时、写一个读端已关闭的管道、网络传来带外数据。

信号的处理

  • 系统默认处理或忽略
  • 捕捉信号:通知内核在某种信号发生时调用一个用户函数,如下

signal

#inclucde <signal.h>
void (*singnal(int sig, void (*func)(int)))(int)
/* 理解
void (*func)(int)  一个函数指针,所指向的函数需要一个整型参数,无返回值。
signal(int sig, void (*func)(int)), signal函数有两个参数,一个整型,一个函数指针。
void (*signal(…))(int)  signal的返回值也是一个函数指针,所指向的函数需要一个整型参数,无返回值。

令Sigfunc代表一个无返回值有1个int参数的函数
所以定义可以简化成:
Sigfunc* signal(int, Sigfunc*)
*/
    
/* sig的取值
	SIGABRT	(Signal Abort) 程序异常终止。
	SIGFPE	(Signal Floating-Point Exception) 算术运算出错,如除数为 0 或溢出(不一定是浮点运算)。
	SIGILL	(Signal Illegal Instruction) 非法函数映象,如非法指令,通常是由于代码中的某个变体或者尝试执行数据导致的。
	SIGINT	(Signal Interrupt) 中断信号,如 ctrl-C,通常由用户生成。
	SIGSEGV	(Signal Segmentation Violation) 非法访问存储器,如访问不存在的内存单元。
	SIGTERM	(Signal Terminate) 发送给本程序的终止请求信号。
*/

// 例1:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
    
void sighandler(int);
int main()
{
	signal(SIGINT, sighandler);
	while(1) 
	{
		printf("开始休眠一秒钟...\n");
		sleep(1);
	}
	return 0;
}
void sighandler(int signum)	// 这个参数的意思是错误的类型,即sig的取值
{
	printf("捕获信号 %d,跳出...\n", signum);
	exit(1);
}

// 例2(捕捉多个信号):
signal(SIGINT, my_sig);
signal(SIGALRM, my_sig);
void my_sig(int signo)
{
	if (signo == SIGINT)
	{
		......
	}
	else if(signo == SIGALRM)
	{
		......
	}
}

alarm

#include <unistd.h>
unsigned int alarm(unsigned int seconds)
// 按秒设置下一次闹钟的时间。
// 返回值:如果以前设置过闹钟,再次设置时返回上一次设置时间的余数;否则返回0。
// alarm函数设置的时间到达时,产生SIGALRM信号。该信号的默认动作是终止程序。

其他注意事项

进程控制中的信号处理 - fork时,子进程继承父进程的信号处理。 - exec时,恢复系统默认设置。

重入问题 - 信号处理程序可以被中断,中断时可能导致信号处理程序重新进入(再次被调用)。 - printf不可重入,大多数标准IO库函数不可重入。 - 信号处理程序应尽可能简单,尽可能不用库函数。


Linux
https://shuusui.site/blog/2022/04/24/linux/
作者
Shuusui
发布于
2022年4月24日
更新于
2022年4月24日
许可协议