Linux
应⽤层开发(学习
Linux
系统函数)
1.
⽂件
IO
编程(掌握)
1.
⽂件
/
⽂件描述
2.
⽂件操作:开
/
闭
/
读
/
写
/
定位等
3.
⽂件指针
/
⽂件流
4.
缓冲
5.
流操作:开
/
闭
/
读
/
写
/
定位等
6.
标准
IO
流
7.
⾮阻塞
IO
2.
多任务编程
(
掌握、重点、难点
)
1.
进程
1.
进程的概念
2.
进程的控制
3.
守护进程
4.
进程间通信
2.
线程
1.
线程的概念和基础
2.
线程控制与编程
⽂件
IO
编程
linux
下⼀切皆⽂件,我们操作外设
(
⿏标、键盘、磁盘等外设
)
就像操作⽂件
⼀样。要如何操作⽂件与外设,就必须熟练掌握⽂件
IO(input
写、
output
读
)
1.
多⽂件编程
多⽂件编程:把⼀个程序的源代码,根据程序员的想法进⾏归类,把相关的
功能函数添加在⼀个⽂件中,⼀个项⽬⼯程程序可能就有多个⽂件构成,这
就叫做多⽂件编程
如:
1.c 2.c 3.c
每个⽂件中写⼀部分功能函数,三个⽂件合起来就是⼀个完整的功
能程序
编译:
gcc xxx1.c xxx2.c xxx3.c
编译机制:
gcc
进⾏编译时,存在
4
个步骤:前三个步骤是程序中的每个⽂件独⽴执⾏编
译
1
、预处理步骤:就是把⽤特殊标识符(以警号
#
开头)表⽰的内容还原
gcc -E xxx.c -o xxx.i
2
、编译步骤:把
C
语⾔的源代码先翻译为汇编代码
gcc -S xxx.i -o xxx.s
3
、汇编步骤:把汇编代码翻译为⼆进制代码
gcc -c xxx.s -o xxx.o
4
、链接步骤:把刚才编译过程中的每个⽂件链接在⼀起形成⼀个可执⾏程序
gcc xxx1.o xxx2.o xxx3.o -o a.out
由于在编译时,虽然是同时编译多个⽂件,但是编译步骤完成时前三个步骤
是每个⽂件⾃⼰完成⾃⼰的步骤操作,最后在链接起来。在源⽂件中使⽤时
是交叉使⽤的(
1
可能⽤
2
的函数,
2
可能⽤
3
的函数,
3
可能⽤
1
的函数),单
个⽂件单独编译时,在⾃⼰⽂件中找不到对应的函数(因为在其他⽂件中)
就会报错,所以需要在⽂件中使⽤了其他⽂件的函数就进⾏声明,进⾏说
明,那每个⽂件如果⽤到了其他⽂件的函数都需要进⾏声明
在
C
中有⼀种⽂件,
专门⽤于书写声明
,叫做头⽂件
xxx.h
。
作⽤就是可以⽤于多⽂件编程中声明编写只写⼀份,让后在多个⽂件中进⾏
include,
在编译时预处理阶段就展开声明
#include “xxx.h”
就表⽰要使⽤这个
xxx.h
中的声明
预处理命令:
在编译过程中,在预处理阶段执⾏的操作
#include
表⽰在预处理阶段就加载对应⽂件中的声明
#if else endif
表⽰在预处理阶段只保留某段代码,另外⼀段代码不保留
#defi ne
宏定义
(
宏替换
)
IO
:输⼊输出
在程序中如果要操作磁盘上的⽂件,对⽂件就是读写操作
读:程序从磁盘上的⽂件中获取到内容放⼊到程序中
----
输⼊
写:程序把程序中的数据存放到磁盘上的⽂件中
---------
输出
2.
⽂件
IO
Linux
系统提供⼀套系统调⽤
API
,⽂件
IO
:
open
、
read
、
write
、
close
、
lseek
等
熟练写出⽂件拷⻉等功能模块
打开⽂件
open
1.
⽂件操作
#
include
<sys/types.h>
#
include
<sys/stat.h>
#
include
<fcntl.h>
打开⽂件:
int
open
(
const char
*
pathname,
int
flags);
参数:
参数
1
:
const char
*
pathname
:字符指针,表⽰的是字符的地址,
字符串的⾸地址
,
要打开的⽂件路径字符串的地址
参数
2
:
int
flags
:整数,打开⽂件的选项
O_RDONLY
:只读
O_WRONLY
:只写
O_RDWR
:读写
O_TRUNC
:清空⽂件
(
在有 写 ⽅式 有效
)
O_APPEND
:追加⽂件
(
在有 写 ⽅式 有效
)
,在写⽂件时,在
⽂件末尾位置添加写
O_CREAT
:如果⽂件不存在则,创建⽂件,存在则直接打开,
如果要使⽤当前选择,则需要第三个参数:创建⽂件权限
返回值:
失败,返回
-
1
C
读取⽂件(输⼊到程序)
read
写⼊⽂件(输出到⽂件)
write
成功打开⽂件,返回⽂件描述符
>=
0
创建且打开⽂件:
int
open
(
const char
*
pathname,
int
flags,
mode_t
mode);
#
include
<unistd.h>
从打开的⽂件中读取⽂件内容,输⼊到程序中
ssize_t
read
(
int
fd,
void
*
buf,
size_t
count);
//
从指定的
fd(
打开的⽂件中,读取
count
个字节数据,存放到程序的内存
buf
地址
开始位置
)
参数:
参数
1
:
int
fd
:⽂件描述符,表⽰打开的⽂件
参数
2
:
void
*
buf
:指针,表⽰把从⽂件中读取的内容,存放到程序
指定的内存地址中
参数
3
:
size_t
count
:整数,表⽰从⽂件中读取多少个字节的数据内
容
返回值:
成功:返回读取到的字节数,如果返回值为
0
表⽰本次读取是从
⽂件末尾开始读取,没有内容
失败:
-
1
C
关闭⽂件
close
#
include
<unistd.h>
从程序中把内存数据写⼊到⽂件中,程序输出到⽂件中
ssize_t
write
(
int
fd,
const void
*
buf,
size_t
count);
//
把
buf
这个内存地址的中的数据,拿出
count
字节数,写⼊到
fd
⽂件中
参数:
参数
1
:
int
fd
:要写⼊哪个⽂件
参数
2
:
const void
*
buf
:要写⼊的内容在哪个内存地址(把哪个内
存地址的内容,写⼊⽂件)
参数
3
:
size_t
count
:要写⼊内容的⼤⼩
返回值:
成功:返回写⼊的字节数
失败:返回
-
1
C
#
include
<unistd.h>
//
把打开的⽂件关闭
int
close
(
int
fd);
参数:
参数
1
:
int
fd
:⽂件描述符,表⽰关闭哪个打开的⽂件
返回值:
成功:返回
0
失败:返回
-
1
C
对于程序⽽⾔:系统默认打开了终端⽂件
终端⽂件打开了三次,分别以不同的⽅式打开,⽂件描述符如下:
0
:读打开,只读,可以读取终端⽂件内容(命令⾏输⼊的内容)
1
:写打开,只写,可以写⼊到终端⽂件
(
终端上显⽰
)
2
:写打开,只写,以更⾼权限写,程序出错时,想⽴即显⽰
作业:
1
、实现程序 把
1.txt
拷⻉到
2.txt
中
2
、从终端输⼊⼀个字符串,把输⼊的字符串存放到
1.txt
⽂件中
对于⽂件操作,有⼀个指向⽂件当前操作的位置的指针,描述当前读写⽂件
在哪⾥
设置⽂件偏移位置
lseek
#
include
<sys/types.h>
#
include
<unistd.h>
重新设置⽂件当前操作位置(修改偏移位置)
off_t
lseek
(
int
fd,
off_t
offset,
int
whence);
//
设置打开的
fd
⽂件的偏移位置
参数:
参数
1
:
int
fd
:表⽰打开的⽂件,要设置的⽂件
参数
2
:
off_t
offset
:整数,偏移量,表⽰偏移多少个字节
+
:正数,向⽂件末尾
偏移
-
:负数,向⽂件开头
偏移
参数
3
:
int
whence
:基准点,表⽰从哪个位置开始计算
SEEK_SET
:从⽂件开始位置计算偏移
SEEK_CUR
:从⽂件当前的操作位置计算偏移
C
位置偏移可以超过当前⽂件⼤⼩,叫做空洞⽂件,中间空洞部分每个字节都
会补
'\0'
创建⽬录
mkdir
删除⽬录
rmdir
SEEK_END
:从⽂件末尾位置开始计算偏移
返回值:
成功:返回从⽂件开始位置到新偏移之后位置⼀共多少个字节
失败:返回
-
1
2.
⽬录(⽂件)操作
#
include
<sys/stat.h>
#
include
<sys/types.h>
在指定⽬录中创建⼀个⽬录⽂件
int
mkdir
(
const char
*
pathname,
mode_t
mode);
参数:
参数
1
:
const char
*
pathname
:指针,字符串⾸地址,要创建的⽬
录⽂件的路径
参数
2
:
mode_t
mode
:创建的⽬录的权限
(
读写执⾏
)
返回值:
成功:返回
0
失败:返回
-
1
C
打开⽬录⽂件
获取打开⽬录中的⽂件
readdir
#
include
<unistd.h>
int
rmdir
(
const char
*
pathname);
参数:
参数
1
:
const char
*
pathname
:字符串⾸地址,表⽰要删除的⽬录
返回值:
成功:返回
0
失败:返回
-
1
C
#
include
<sys/types.h>
#
include
<dirent.h>
去打开对应路径下的⽬录
DIR
*
opendir
(
const char
*
name);
参数:
参数
1
:
const char
*
name
:字符串⾸地址,表⽰要打开的⽬录⽂件
路径
返回值:
DIR
:⽬录信息结构体类型
成功:返回⽬录信息结构体的地址
(
指针
)
,标识打开的⽬录⽂
件
失败:返回
NULL
(
空指针
)
C
关闭打开的⽬录⽂件
closedir
#
include
<dirent.h>
获取打开的⽬录中,⼀个⽂件
struct
dirent
*
readdir
(DIR
*
dirp);
参数:
参数
1
:
DIR
*
dirp
:获取哪个(打开的)⽬录中的⽂件
返回值:
成功:返回获取到的这个⽂件的描述
(
结构体
)
的地址
NULL
:表⽰本次获取已经获取到⽬录的结尾了没有⽂件了
(
已
经获取完
)
⽂件描述结构体
struct
dirent
{
ino_t
d_ino;
//inode
号,⽂件系统中对
⽂件的唯⼀编号
off_t
d_off;
//
偏移
unsigned short
d_reclen;
//
⻓度⼤⼩
unsigned char
d_type;
//
⽂件类型
char
d_name[
256
];
//
⽂件名
};
C
#
include
<sys/types.h>
#
include
<dirent.h>
关闭打开的⽬录
int
closedir
(DIR
*
dirp);
参数:
C
参数
1
:
DIR
*
dirp
:表⽰要关闭的⽬录⽂件
返回值:
成功:返回
0
失败:返回
-
1
#
include
<string.h>
⽐较两个字符串
int
strcmp
(
const char
*
s1,
const char
*
s2);
⽐较
s1
字符串,和
s2
字符串是否相等,从第⼀个字符开始⽐较,⼀直⽐
较到字符串结束,如果每个字符都相等,则整个字符串相等,返回
0
⽐较两个字符串前
n
个字符是否相等,相等返回
0
int
strncmp
(
const char
*
s1,
const char
*
s2,
size_t
n);
把
src
字符串内容追加到
dest
字符串最后字符位置,让
dest
字符串添加
内容
char
*
strcat
(
char
*
dest,
const char
*
src);
把
src
字符串内容前
n
个字符追加到
dest
字符串最后字符位置,让
dest
字
符串添加内容
char
*
strncat
(
char
*
dest,
const char
*
src,
size_t
n);
把
src
字符串内容拷⻉到
dest
中,覆盖原
dest
内容,让
dest
变为
src
字符
串内容
char
*
strcpy
(
char
*
dest,
const char
*
src);
把
src
字符串前
n
个字符拷⻉到
dest
中,覆盖原
dest
内容,让
dest
变为
src
字符串内容
char
*
strncpy
(
char
*
dest,
const char
*
src,
size_t
n);
C
练习:获取⽬录下是否有
1.txt,
如果存在则显⽰⽂件内容在终端
库函数:由计算机语⾔标准委员会审核通过,如
C
标准委员会,只要是使⽤
C
语⾔就可以使⽤那⼀套函数(平台⽆差异)
系统调⽤:由系统提供,只能在对应的平台中使⽤,这⼀套
API
函数就叫做系
统调⽤
标准
IO
:由
C
语⾔标准委员会设计的⼀套⽂件的操作
API
函数
打开⽂件
fopen
获取
s
地址字符串的⻓度
(
不计算
'\0'
)
,返回值就是⻓度
size_t
strlen
(
const char
*
s);
3.
标准
IO
#
include
<stdio.h>
打开指定的⽂件
FILE
*
fopen
(
const char
*
pathname,
const char
*
mode);
参数:
参数
1
:
const char
*
pathname
:字符串⾸地址,表⽰要打开的⽂件
路径
参数
2
:
const char
*
mode
:字符串⾸地址,通过通过字符串来表⽰
打开⽂件的⽅式
"r"
:只读⽅式打开
(
⽂件必须存在
)
---------
O_RDONLY
"r+"
:读写⽅式打开
--------
O_RDWR
"w"
:只写⽅式打开
(
清空⽂件,当⽂件不存在时创建
)
----
C
在使⽤标准
IO
打开⽂件时,会添加
缓冲区
缓冲区:
O_WRONLY
|
O_CREAT
|
O_TRUNC
"w+"
:读写⽅式打开
(
清空⽂件,当⽂件不存在时创建
)
---
O_RDWR
|
O_CREAT
|
O_TRUNC
"a"
:追加写⽅式打开
(
操作位置在⽂件末尾,当⽂件不存在时
创建
)
---
O_WRONLY
|
O_CREAT
|
O_APPEND
"a+"
:读写⽅式打开
(
写为追加写操作位置在⽂件末尾,当⽂
件不存在时创建
)
-----
O_RDWR
|
O_CREAT
|
O_APPEND
如果上述字符串中 包含
'b'
表⽰打开⼆进制⽂件,否则打开是
⽂本⽂件
返回值:
FILE
:是⼀个结构体,描述打开的⽂件信息(包括了⽂件描述
符)
返回值就是返回
FILE
这个结构体类型变量的地址
成功:返回
FILE
*
指针,⽂件信息结构体地址
(
能知道打开
的⽂件
)
失败:返回
NULL
(
空指针
)
关闭⽂件
fclsoe
作业:
#
include
<stdio.h>
关闭⽂件,则会把当前打开的⽂件的缓冲区存放到⽂件中
int
fclose
(FILE
*
stream);
参数:
参数
1
:
FILE
*
stream
:关闭打开的哪个⽂件
返回值:
成功:返回
0
失败:返回
-
1
(
EOF
)
C
cat
功能 查看⽂件内容
在
1.txt
⽂件中查找是否存在
"hello"
字符串
写⼊⽂件
fwrite
读取⽂件
fread
#
include
<stdio.h>
把数据写⼊到⽂件
size_t
fwrite
(
const void
*
ptr,
size_t
size,
size_t
nmemb,FILE
*
stream);
参数:
参数
1
:
const void
*
ptr
:要写⼊⽂件的内容对应地址
参数
2
:
size_t
size
:每⼀个数据⼤⼩
参数
3
:
size_t
nmemb
:写⼊多少个数据
参数
4
:
FILE
*
stream
:写⼊的⽂件
返回值:
成功:返回写⼊的数据的个数
C
#
include
<stdio.h>
从⽂件中读取数据存放到
ptr
size_t
fread
(
void
*
ptr,
size_t
size,
size_t
nmemb, FILE
*
stream);
参数:
参数
1
:
void
*
ptr
:从⽂件中读取的数据存放的位置
(
指针
)
C
刷新标准
io
缓冲区(把缓冲区内容写⼊⽂件):
判断是否错误或读取到⽂件结束
缓冲区的类型:
⽆缓冲:没有缓冲区,直接写⼊⽂件
⾏缓冲:当内容包含回⻋换⾏符号
(\n)
,就会⽴即写⼊,或当缓冲区满,也
参数
2
:
size_t
size
:每个数据⼤⼩
(
字节
)
参数
3
:
size_t
nmemb
:读取多少个数据
参数
4
:
FILE
*
stream
:读取的⽂件
返回值:
成功:返回读取的数据个数
0
:表⽰读取时没有数据可读
(
到达⽂件末尾
)
, 或 读取错误
#
include
<stdio.h>
主动把缓冲区的内容写⼊⽂件
int
fflush
(FILE
*
stream);
C
#
include
<stdio.h>
测试当前是否是⽂件末尾,如果是⽂件末尾返回⾮
0
(返回真)
int
feof
(FILE
*
stream);
测试当前是否是错误,如果是错误返回⾮
0
int
ferror
(FILE
*
stream);
C
会写⼊⽂件
全缓冲:当缓冲区满,才会写⼊⽂件
当系把统运⾏程序时,默认会终端⽂件打开
3
次:
0
(终端读⽂件描述符)
--------stdin
(标准输⼊)
----
⾏缓冲
1
(终端写⽂件描述符)
--------stdout
(标准输出)
----
⾏缓冲
2
(终端写⽂件描述符)
--------stderr
(标准错误输出)
----
⽆缓冲
读取单个字符
fgetc
写⼊单个字符
fputc
#
include
<stdio.h>
从⽂件中读取⼀个字符,以返回值形式,返回读取到的字符
(
int
)
int
fgetc
(FILE
*
stream);
参数:
参数
1
:
FILE
*
stream
:从哪个⽂件中读取
返回值:
成功:返回读取到的字符,以
int
类型
(
字符对应的
ASCII
码
)
表
⽰
如果本次是在⽂件末尾位置读取
(
⽂件结束位置
),
返回
EOF
(
-
1
)
如果读取失败,返回
EOF
需要判断
EOF
到底是失败还是读取到⽂件末尾
int
getc
(FILE
*
stream);
====
fgetc
int
getchar
(
void
);
==
fgetc
(
stdin
)
:
从终端⽂件读取
(
输⼊
)
⼀个
字符
C
读取⼀个字符串
fgets
#
include
<stdio.h>
往⽂件中写⼊⼀个字符
int
fputc
(
int
c, FILE
*
stream);
参数:
参数
1
:
int
c
:要写⼊的字符的
ASCII
码
参数
2
:
FILE
*
stream
:要写⼊的⽂件
返回值:
成功:返回写⼊的字符的
ASCII
码
失败:返回
EOF
(
-
1
)
int
putc
(
int
c, FILE
*
stream);
等价于
fputc
int
putchar
(
int
c);
等价于
=====
fputc
(c,
stdout
),
往终端⽂件
写⼊⼀个字符
C
#
include
<stdio.h>
从⽂件中读取⼀个字符串
char
*
fgets
(
char
*
s,
int
size, FILE
*
stream);
从⽂件
stream
中读取内容,最多读取
size
-
1
个字符,存储到
s
指针这个地址中。
具体读取的字符⼤⼩:⼆选⼀
1
、读取到⽂件结束
2
、读取到⼀⾏结束
(\n)
如果在读取过程中当读取到
size
-
1
时,两个都不满⾜,则读取
size
-
1
个字符(读取最⼤⼤⼩)
C
写⼊⼀个字符串
作业:
通过标准
IO
实现⽂件拷⻉
从终端输⼊数据,写⼊⽂件
注意:在读取的字符串后,加上
'\0'
字符,表⽰字符串的结束
返回值:
成功:返回
s
指针
NULL
:本次读取在⽂件结束位置读取
(
已经读取到⽂件末尾
)
char
*
gets
(
char
*
s);
等价于
==
fgets
(s,,
stdin
)
,从终端上读取
⼀个字符串,没有限制⼤⼩
(
没有
size
-
1
)
容易越界
#
include
<stdio.h>
把
s
中的字符串
(
'\0'
为⽌
)
,写⼊到⽂件中
int
fputs
(
const char
*
s, FILE
*
stream);
参数:
参数
1
:
const char
*
s
:要写⼊的字符串
,
到
'\0'
为⽌
参数
2
:
FILE
*
stream
:写⼊的⽂件
返回值:
成功:⾮负整数
>=
0
失败:
EOF
(
-
1
)
int
puts
(
const char
*
s);
等价于
====
fputs
(s,
stdout
),
往终端
上写字符串
C
3.
多任务
程序:是⼀些保存在磁盘上的指令的有序集合,是⼀个⽂件,没有任何执⾏
的概念,是⼀种静态的表现
如果⼀个程序进⾏执⾏,那我们就把正在执⾏的内容就叫做进程
进程:是⼀个程序的⼀次执⾏过程,当系统在执⾏某个程序是,分配和释放
的各种资源
进程是⼀个独⽴的可调度的任务
linux
下的进程结构
进程:包含
3
个部分
数据段:全局变量、常量以及动态分配的空间
(malloc)
正⽂段:程序中的代码
堆栈段:局部变量、函数的形式参数、函数返回值
因为要调度,进程除了有内容以为,还有⼀些信息
(
描述进程的信息
)
-----
进程控制块:程序计数器值、
CPU
的寄存器值,以及存储临时数据的进
程堆栈
进程号:
linux
对执⾏的程序
(
进⾏
)
,进⾏编号
(PID)
调度进程:
ps
:查看当前执⾏的进程
ps axu
ps axj
top
:动态显⽰系统中的进程
kill
:向进程发送信号
kill pid
默认发送结束信号
bg
:将挂起的进程在后台执⾏
bg +
后台编号
(
进程
)
fg
:把后台的进程放在前台执⾏
fg +
后台编号
(
进程
)
程序名
+ &------
在后台运⾏程序
1.
多进程
进程的运⾏状态:
1.
运⾏态
(
就绪态
)
:此时进程或正在运⾏,或准备运⾏
(
只要获取
cpu
时间
就可以运⾏
)
2.
等待态:此时进程在等待⼀个事件的产⽣或某种系统资源
1.
可中断
2.
不可中断
3.
停⽌态:当前进程被中⽌
4.
僵⼫态
(
死亡态
)
:这是⼀个已经被终⽌
(
结束
)
的进程,但是进程的资源还
没被完全释放掉
Linux
进程的创建
(
程序的执⾏
)
,是由另⼀个进程来帮助创建,来执⾏当前这
个程序,来完成创建的进程就叫做⽗进程,被创建的进程就是⼦进程
是由⽗进程去创建⼀个⼦进程,然后才能调度⼦进程
进程创建
fork
结束进程
exit
#
include
<sys/types.h>
#
include
<unistd.h>
通过当前执⾏的进程
(
程序
)
,去创建⼀个新的进程,被创建的进程叫做
⼦进程,执⾏创建的这个进程叫做⽗进程
当调⽤
fork
时,会把⽗进程所有资源
(
代码,变量,打开的⽂件等
)
,复
制⼀份给⼦进程
(
⼦进程的内存空间中内容是和⽗进程完全⼀致
)
,⼦进
程执⾏⾃⼰的内存空间内容,⼦进程拷⻉⽗进程的所有内容,然后从
fork
的返回值开始执⾏
pid_t
fork
(
void
);
返回值:
成功:在⽗进程中
fork
的返回值为⼦进程的
pid;
在⼦进程中
fork
的返回值为
0
失败:⽗进程返回
-
1
,
没有⼦进程
C
#
include
<stdlib.h>
结束当前执⾏的进程,有使⽤标准
io,
在结束时就会刷新缓冲区
(
会写⼊
⽂件
)
void
exit
(
int
status);
参数:
参数
1
:
int
status
:整数,
&
0xff
,
即 只保留低
8
位⾼位全部清
0
,
作
为进程的结束状态,返回给⽗进程
#
include
<unistd.h>
结束当前执⾏的进程,但是不会刷新缓冲区
void
_exit
(
int
status);
C
等待⼦进程结束
wait
在
C
程序,程序只会执⾏
main
函数中的内容,在
main
函数中:
return
语
句
---
结束当前函数
(
跳出当前函数
)
,结束
main
函数,结束当前进程
#
include
<sys/types.h>
#
include
<sys/wait.h>
使⽤该函数,使进程阻塞
(
等待
)
,直到任意⼀个⼦进程结束,才继续执
⾏
pid_t
wait
(
int
*
wstatus);
参数:
参数
1
:
int
*
wstatus
:指针,变量地址,⽤于存储⼦进程结束状态值
的地址
返回值:
成功:返回阻塞等待到的结束的⼦进程的
pid
失败:返回
-
1
waitpid
功能和
wait
函数类似,但是
waitpid
是可以等待指定⼦进程结束
pid_t
waitpid
(
pid_t
pid,
int
*
wstatus,
int
options);
参数:
参数
1
:
pid_t
pid
:进程号
pid
>
0
:只等待进程
id
等于
pid
的⼦进程
pid
== -
1
:等待任何⼀个⼦进程结束,作⽤和
wait
⼀样
pid
< -
1
:等待其进程组等于
pid
的绝对值的任意⼦
进程
C
linux
守护进程
(
精灵进程
)
守护进程:是
Linux
中的后台服务进程,它是⼀个⽣存期较⻓的进程,通常
独⽴与终端并周期性的执⾏某种任何或等待处理某些发⽣的事件
-----daemon
进程
守护进程常常在系统运⾏启动时开始运⾏,在系统关闭时终⽌
Linux
系统中有很多守护进程,⼤多数服务都是守护进程实现的
实现守护进程:
在
Linux
中,每⼀个系统与⽤户进⾏交流的界⾯叫做终端,从这个终端开始
运⾏的进程都会依附于这个终端,这个终端称为这些进程的控制终端,当控
制终端被关闭时,相应的进程都会被⾃动关闭。
实现守护进程要突破这个限制,它从开始运⾏,直到整个系统关闭才会退
出。如果想让某个进程不会因为⽤户或终端的改变⽽受到影响,就必须把这
个进程变为守护进程
守护进程:
1.
创建⼦进程,⽗进程退出
⼦进程在形式上脱离了终端,由于⽗进程先退出,⼦进程变为孤⼉进程
pid
==
0
:等待其进程组
id
等于调⽤进程的组
id
的
任意⼦进程
参数
2
:
int
*
wstatus
:同
wait,
等待接收⼦进程的状态值
参数
3
:
int
options
:选项
0
:同
wait,
表⽰阻塞,⼀直等待⼦进程结束
WNOHANG
:表⽰不会阻塞等待。若,对应
pid
的⼦进
程现在没有结束,则不等待⼦进程结束,当前函数直接返回,若,调⽤时
对应
pid
的⼦进程已经结束,则回收得到⼦进程状态,当前函数直接返回
返回值:
成功:返回阻塞等待到的结束的⼦进程的
pid
失败:返回
-
1
(
重新认⽗进程
)
p = fork()
if(p >0)
exit(0);
2.
在⼦进程中创建新会话
让⼦进程不受其他控制,由⾃⼰控制
(
会话组组⻓
)
进程组:⼀个或多个进程的集合,进程组由进程组
ID
来标识。每个进程
组都有⼀个组⻓进程,进程组
ID
就是组⻓进程的进程号
会话:⼀个或多个进程组的集合
setsid()//
在不是进程组组⻓的基础上,创建⼀个新会话,同时当前进程
就是新会话组的组⻓
#
include
<sys/types.h>
#
include
<unistd.h>
为当前进程设置新会话
pid_t
setsid
(
void
);
C
3.
设置守护进程的⼯作⽬录
⽗进程执⾏时,有⼀个⼯作路径,那在创建⼦进程时,也会使⽤
⽗进程的⼯作路径,⼦进程⾃⼰设置对应的⼯作路径
```C
#include <unistd.h>
改变当前进程的⼯作路径
int chdir(const char *path);
参数:
参数
1
:
const char *path
:新的⼯作路径
4.
重设⽂件权限掩码
⽂件权限掩码是指⽂件权限中被屏蔽掉的对应位,改变了设置的⽂件权
限,通常把⽂件权限掩码设置为
0,
可以增加该守护进程的灵活
进程执⾏的命令⾏参数:
在执⾏程序时,除了有要执⾏的程序,还可以加上参数
参数是添加到执⾏的程序中
------>
执⾏
main
函数
main
函数有参数列表,⽤于在执⾏程序时接收参数
#
include
<sys/types.h>
#
include
<sys/stat.h>
设置当前进程执⾏时,⽂件的⽂件权限掩码
mode_t
umask
(
mode_t
mask);
C
5.
关闭已经打开的⽂件描述符
新建的⼦进程会从⽗进程那⾥继承所有已经打开的⽂件
在创建新的会话后
(
⼦进程
)
,守护进程已经脱离了任何控制终
端,应当关闭⽤不到的⽂件
```C
#include <unistd.h>
获取打开的⽂件个数,打开的最⼤的⽂件描述符就是个数
-1
int getdtablesize(void);
int
main
(
int
argc,
char
*
argv[])
argc
:表⽰执⾏的命令⾏有多少个参数
(
包括程序名
)
C
作业:
通过多进程实现⽂件拷⻉把
1
⽂件复制到
2
⽂件,如果⼩于
10M
两个进程进⾏
拷⻉
,10M-20M,
使⽤
4
个进程进⾏拷⻉,
20M
以上使⽤
5
个进程拷⻉
exec
函数族:
argv
:指针数组,数组的每⼀个元素是⼀个指针
(
char
*
),
数组每⼀个
元素就是⼀个字符串的⾸地址
,
字符串就是命令⾏上的参数
#
include
<unistd.h>
exec
函数族,就是⽤⼀个新的程序⽂件来替换当前进程执⾏的代码⽂
件,变为执⾏新的程序内容
把当前进程执⾏内容替换为新内容
(
程序⽂件
)
,把参数列举出来
int
execl
(
const char
*
pathname,
const char
*
arg, ...
/* (char *) NULL */
);
参数:
参数
1
:
const char
*
pathname
:程序的路径
参数
2
:
const char
*
arg, ...
:对于应⽤程序⽽⾔,要执⾏时,都
可能需要命令⾏参数,即:程序名 参数
1
参数
2.
...
表⽰要执⾏的新的程序的命令⾏参数
argv0,argv1
,
argv2...
NULL
(
NULL
表⽰参数结束
)
返回值:
正确,会替换成其他的程序⽂件内容,没有返回
失败:返回
-
1
int
execlp
(
const char
*
file,
const char
*
arg, ...
/* (char *) NULL */
);
作⽤与
execl
完
全⼀致,
execlp
不⽤写路径,只⽤写程序名,会从指定的路径下寻找程
C
每个进程都是独⽴的私有的空间,是⼀个独⽴的任务,虽然可能多个进程相
互之间需要协同⼯作,还是需要⾃⼰的进程切换,因此在进程的上下⽂切换
时,系统开销⽐较⼤
为了提供系统的性能,在操作系统中引⼊了轻量级的进程,也叫做线程
线程提⾼系统性能的⽅式就是,让多个轻量级进程
(
线程
)
共享⼀些进程资源,
在切换时只⽤切换⾃⼰私有的资源即可
在同⼀个进程中创建的多个线程共享进程的地址空间。
线程和进程⼀样都是统⼀参与调度执⾏
序
把当前进程执⾏内容替换为新程序内容,使⽤指针数组存储参数
int
execv
(
const char
*
pathname,
char
*
argv[]);
参数:
参数
1
:
const char
*
pathname
:程序路径
参数
2
:
char
*
const
argv[]
:指针数组,每个元素是指针,字符串
⾸地址
(
命令⾏参数
)
int
execvp
(
const char
*
file,
char
*
const
argv[]);
作⽤与
execv
完全⼀致,
execvp
不⽤写路径,只⽤写程序名,会从指定的路径
下寻找程序
2.
多线程
通常 线程就是指共享相同的地址空间的多个任务,线程依赖于进程
线程基础:
⼀个进程中的多个线程共享资源:
1.
可执⾏的指令
2.
静态数据
3.
进程中打开的⽂件
4.
当前⼯作的⽬录
5.
⽤户
id
6.
⽤户组
id
7.
pid
进程号
每个线程私有的资源:
8.
pc(
程序计数器
)
和相关寄存器
9.
线程
id(tid)
10.
堆栈:局部变量、函数参数、返回值地址
11.
错误码
12.
执⾏状态和属性
NEW POSIX THREAD LIBRARY
(
NPTL
)提供的基本操作
创建线程
删除线程
控制线程
创建线程
pthread_create
#
include
<pthread.h>
在进程中创建线程执⾏
int
pthread_create
(
pthread_t
*
thread,
const
pthread_attr_t
*
attr,
void
*
(
*
start_routine) (
void
*
),
void
*
arg);
{
*
thread
=
tid;
}
参数:
参数
1
:
pthread_t
*
thread
:指针,地址,(存储线程
id
)创建线程
后会把线程
id
存储到这个地址中
参数
2
:
const
pthread_attr_t
*
attr
:要执⾏的线程的属性,传递
的是属性变量值的地址,通常 写
NULL
表⽰使⽤默认属性创建线程
参数
3
:
void
*
(
*
start_routine) (
void
*
)
:函数指针,函数地
址,线程执⾏的起始函数,线程从哪个函数开始执⾏
参数
4
:
void
*
arg
:提供给参数
3
这个函数的参数
C
关闭当前线程
pthread_exit
等待线程结束
返回值:
成功:返回
0
失败:返回错误码
#
include
<pthread.h>
结束当前线程,同时把线程结束状态,返回给创建当前线程的进程或线程
中
void
pthread_exit
(
void
*
retval);
参数:
参数
1
:
void
*
retval
:指针,地址,作为线程的结束状态,返回给创
建的线程中
C
#
include
<pthread.h>
等待线程结束,且接收线程的结束状态
int
pthread_join
(
pthread_t
thread,
void
**
retval);
参数:
参数
1
:
pthread_t
thread
:要等待结束的线程
id
参数
2
:
void
**
retval
:⼆级指针,⼀级指针的地址,把线程结束状
态
(
⼀级指针
)
,存储到这个地址中
返回值:
C
取消线程
线程间的同步互斥
对于线程⽽⾔,由于是使⽤的同⼀个进程的地址空间,线程间通信是容易
的,通过全局变量实现数据的共享和交换
同步:指多个任务按照⼀定的顺序相互配合完成意⻅⼯作,通过信号量实现
信号量:代表着⼀类资源,其值就是这种资源的数量,是⼀个⼤于等于零
(
⾮
负整数
)
通过设计⽣产者消费者模型,线程执⾏先⽣产然后再消费,⼀个线程做⽣
产,另⼀个线程做消费,存在先后关系
⽣产者消费者模型
-----pv
操作:
p
操作:消费
(
申请资源
)
if(
信号量值⼤于
0)
{
申请资源,任务继续执⾏
信号量值
--(
表⽰资源减少
)
}
else
{
申请资源,任务阻塞等待
}
成功:返回
0
失败:返回错误码
#
include
<pthread.h>
取消线程执⾏,结束指定线程
int
pthread_cancel
(
pthread_t
thread);
C
v
操作:⽣产
(
释放资源
)
if(
没有等待资源的任务
)
{
信号量
++
}
else
{
信号量
++
唤醒等待的任务
}
信号量是⼀个受保护的变量值,只允许三个操作:
初始化
#
include
<semaphore.h>
初始化信号量
(
设置资源值数⽬
)
int
sem_init
(
sem_t
*
sem,
int
pshared,
unsigned int
value);
参数:
参数
1
:
sem_t
*
sem
:地址,信号量的地址,把初始化的值存⼊这个变
量地址中
参数
2
:
int
pshared
:信号量使⽤的范围
0
:表⽰在线程间使⽤
⾮
0
:表⽰在进程间使⽤
参数
3
:
unsigned int
value
:信号量初始化的值
返回值:
成功:返回
0
失败:返回
-
1
C
P
操作
(
申请资源,消费
)
C
#
include
<semaphore.h>
资源信号量
--
,但是
sem
==
0
就不能继续
--
⽽是阻塞等待
int
sem_wait
(
sem_t
*
sem);
V
操作
(
释放资源,⽣产
)
C
#
include
<semaphore.h>
资源信号量
++
int
sem_post
(
sem_t
*
sem);
互斥:每个资源在任意时刻最多只能有⼀个线程进⾏访问,通过互斥锁实现
互斥锁:就是每个线程在⾃⼰线程中访问资源,当能够获取到锁
(
加锁
)
时就访
问,访问完就释放锁
(
解锁
)
。当线程⽆法获得锁时,就阻塞等待直到获得锁为
⽌
互斥锁:保护共享资源,保护数据的完整性
每个线程在访问同⼀个资源时,都使⽤互斥锁机制,就可以达到互斥⽬的
初始化互斥锁:
C
#
include
<pthread.h>
初始化互斥锁,设置互斥锁变量
int
pthread_mutex_init
(
pthread_mutex_t
*
mutex,
const
pthread_mutexattr_t
*
attr);
参数:
获取互斥锁然后才访问数据
(
加锁
)
:
释放互斥锁
(
解锁
)
:
1.
早期通信⽅式
1.
管道
2.
信号
2.
system V IPC
对象
参数
1
:
pthread_mutex_t
*
mutex
:互斥锁变量地址,要初始化的
互斥锁变量
参数
2
:
pthread_mutexattr_t
*
attr
:互斥锁的属性,
NULL
表⽰默
认属性
返回值:
成功:返回
0
失败:返回
-
1
#
include
<pthread.h>
int
pthread_mutex_lock
(
pthread_mutex_t
*
mutex);
C
#
include
<pthread.h>
int
pthread_mutex_unlock
(
pthread_mutex_t
*
mutex);
C
3.
进程间通信
1.
共享内存
2.
消息队列
3.
信号灯
(
信号量
)
为了多个进程间能够进⾏通信(交换数据),内核专门留出了⼀块内存空
间,可以由访问的进程将其映射到进程中
-----
共享内存
共享内存的使⽤步骤:
1.
创建
/
打开共享内存
1.
共享内存
#
include
<sys/ipc.h>
#
include
<sys/shm.h>
//
创建或打开指定
key
的共享内存,如果内核已经存储
key
的共享内存则
打开,不存在则创建
int
shmget
(
key_t
key,
size_t
size,
int
shmflg);
参数:
参数
1
:
key_t
key
:要创建或要打开的内容内存编号
参数
2
:
size_t
size
:要创建的共享内存⼤⼩
(
字节
)
参数
3
:
int
shmflg
:选项
0666
:权限
IPC_CREAT
:不存在就创建
返回值:
成功:返回共享内存
id
失败:返回
-
1
#
include
<sys/types.h>
C
2.
映射共享内存,即把共享内存的地址映射到进程中,可以进⾏使⽤
3.
使⽤共享内存进⾏数据交换
(
通信
)
4.
解除
(
撤消
)
共享内存映射
#
include
<sys/ipc.h>
//key
的计算,通过⼀个字符串
(
⽂件路径
)
和整数
key_t
ftok
(
const char
*
pathname,
int
proj_id);
#
include
<sys/types.h>
#
include
<sys/shm.h>
映射共享内存
void
*
shmat
(
int
shmid,
const void
*
shmaddr,
int
shmflg);
参数:
参数
1
:
int
shmid
:共享内存
id,
表⽰要进⾏映射的共享内存
参数
2
:
const void
*
shmaddr
:指针,地址,表⽰共享内存要映射到
进程中的哪个地址位置
NULL
:由系统随机映射⼀个地址
参数
3
:
int
shmflg
:操作共享内存的⽅式
SHM_RDONLY
:只读
0
:读写
返回值:
成功:返回映射的地址,操作这个地址对应的空间就是操作共
享内存
失败:返回
-
1
C
8.
删除共享内存对象
#
include
<sys/types.h>
#
include
<sys/shm.h>
解除映射
int
shmdt
(
const void
*
shmaddr);
参数:
参数
1
:
const void
*
shmaddr
:要解除映射的内存地址
返回值:
成功:返回
0
失败:返回
-
1
C
#
include
<sys/ipc.h>
#
include
<sys/shm.h>
控制共享内存
int
shmctl
(
int
shmid,
int
cmd,
struct
shmid_ds
*
buf);
参数:
参数
1
:
int
shmid
:要操作的共享内存
id
参数
2
:
int
cmd
:控制⽅式
IPC_STAT
:查看获取
shmid
共享内存的信息
IPC_SET
:设置
shmid
共享内存的信息
IPC_RMID
:删除共享内存,第三个参数为
NULL
参数
3
:
struct
shmid_ds
*
buf
:结构体指针
如果是
IPC_STAT
,表⽰把
shmid
信息存储
到这个地址中
C
消息队列就是⼀种消息的列表,进程间可以通过消息队列,往消息队列中添
加消息,读取消息
1.
创建
/
打开消息队列
3.
读取
/
添加消息
如果是
IPC_SET
,表⽰把这个地址中的
(
结
构体
)
信息作为
shmid
的信息进⾏设置
2.
消息队列
#
include
<sys/types.h>
#
include
<sys/ipc.h>
#
include
<sys/msg.h>
创建或打开消息队列
int
msgget
(
key_t
key,
int
msgflg);
参数:
参数
1
:
key_t
key
:
key
编号,消息队列的编号
参数
2
:
int
msgflg
:选项
返回值:
成功:返回消息队列的
id
失败:返回
-
1
C
#
include
<sys/types.h>
#
include
<sys/ipc.h>
C
#
include
<sys/msg.h>
往消息队列中添加消息
int
msgsnd
(
int
msqid,
const void
*
msgp,
size_t
msgsz,
int
msgflg);
参数:
参数
1
:
int
msqid
:消息队列
id,
操作的消息队列是谁
参数
2
:
const void
*
msgp
:整个消息数据的⾸地址
包含:类型和正⽂数据
参数
3
:
size_t
msgsz
:消息正⽂⼤⼩
参数
4
:
int
msgflg
:选项
0
:阻塞等待,直到发送完
(
往消息队列中添加消息
完
)
这个函数才结束
IPC_NOWAIT
:消息还没发送完成
(
往消息队列中添加
消息还未完成
)
就结束这个函数
返回值:
成功:返回
0
失败:返回
-
1
从消息队列中获取消息
ssize_t
msgrcv
(
int
msqid,
void
*
msgp,
size_t
msgsz,
long
msgtyp,
int
msgflg);
参数:
参数
1
:
int
msqid
:要获取的消息队列
id
参数
2
:
void
*
msgp
:获取的消息存放地址
参数
3
:
5.
删除消息队列
size_t
msgsz
:消息的正⽂⼤⼩
参数
4
:
long
msgtyp
:要获取的消息的类型
0
:任意消息类型都可以获取
>
0
:从消息队列中获取第⼀个为对应类型的消息
参数
5
:
int
msgflg
:选项
0
:阻塞等待,直到接收完
(
从消息队列中获取消息
完
)
这个函数才结束
IPC_NOWAIT
:消息还没接收完成
(
从消息队列中获取
消息还未完成
)
就结束这个函数
返回值:
成功:返回获取的正⽂⼤⼩
失败:返回
-
1
#
include
<sys/types.h>
#
include
<sys/ipc.h>
#
include
<sys/msg.h>
控制消息队列
int
msgctl
(
int
msqid,
int
cmd,
struct
msqid_ds
*
buf);
参数:
参数
1
:
int
msqid
:消息队列
id
参数
2
:
int
cmd
:控制⽅式
IPC_STAT
:获取消息队列信息
IPC_SET
:设置消息队列信息
IPC_RMID
:删除消息队列
C
在进程间使⽤⼀个特定的信号量值,达到进程间的同步互斥
信号量的操作
1.
创建信号量
参数
3
:
struct
msqid_ds
*
buf
:结构体地址,⽤于获取消息队列信
息
(
把信息存储到这个地址
)
;⽤于设置消息队列信息
(
把地址对应空间信
息设置到消息队列
)
3.
信号灯
(
量
)
#
include
<sys/types.h>
#
include
<sys/ipc.h>
#
include
<sys/sem.h>
根据
key
值,创建
/
打开信号灯
int
semget
(
key_t
key,
int
nsems,
int
semflg);
参数:
参数
1
:
key_t
key
:
key
编号
参数
2
:
int
nsems
:信号灯集中信号量的数⽬
参数
3
:
int
semflg
:选项,权限
IPC_CREAT
:创建
0666
:权限
返回值:
成功:返回
semid
失败:返回
-
1
C
2.
信号量的操作
3.
信号量的控制
#
include
<sys/types.h>
#
include
<sys/ipc.h>
#
include
<sys/sem.h>
信号灯的操作
int
semop
(
int
semid,
struct
sembuf
*
sops,
size_t
nsops);
参数:
参数
1
:
int
semid
:要操作的信号灯
参数
2
:
struct
sembuf
*
sops
:具体的操作⽅式
struct
sembuf
sops
{
unsigned short
sem_num;
//
要操作信号
量的编号
short
sem_op;
//
操作
0
:等待信号量的值变为
0
-
1
:
p
操作,消费,把信号量值
-
1
1
:
v
操作,⽣产,把信号量值
+
1
short
sem_flg;
//
选项
0
:阻塞等待
IPC_NOWAIT
:⾮阻塞,不等待
}
参数
3
:
size_t
nsops
:要操作的信号灯的个数
C
在内核的内存空间中,设计了⼀个通道,这个通道可以让进程使⽤
(
进程可以
读写
)
,是⼀种半双⼯⽅式,使⽤⽂件的操作⽅式进⾏通信(
read
、
write
)
有名管道
(fi fo)
管道有⼀个名字,要通信的进程通过这个名字找到管道⽂件
#
include
<sys/types.h>
#
include
<sys/ipc.h>
#
include
<sys/sem.h>
控制信号量
int
semctl
(
int
semid,
int
semnum,
int
cmd, ...);
参数:
参数
1
:
int
semid
:控制的信号量
id
参数
2
:
int
semnum
:要控制的是信号灯集中的哪个信号量编号
参数
3
:
int
cmd
:控制⽅式
IPC_RMID
:删除信号灯集,同时第⼆个和第四个参
数不起作⽤
GETVAL
:获取指定编号的信号量的值
SETVAL
:设置指定编号的信号量的值
参数
4
:
根据参数
3
决定
返回值:
返回值:成功,根据不同的
cmd
返回不同的内容
失败:返回
-
1
C
4.
管道
创建管道⽂件:在⽂件系统中创建⼀个管道⽂件,但是使⽤的时候在内存中
使⽤这个管道⽂件
⽆名管道
(pipo)
没有名字,只能存在于内存中
创建⽆名管道
#
include
<sys/types.h>
#
include
<sys/stat.h>
int
mkfifo
(
const char
*
pathname,
mode_t
mode);
参数:
参数
1
:
const char
*
pathname
:创建的管道⽂件的路径
参数
2
:
mode_t
mode
:创建管道⽂件的权限
返回值:
成功:返回
0
失败:返回
-
1
C
#
include
<unistd.h>
创建打开⽆名管道,同时获取到打开的⽆名管道的⽂件描述符
int
pipe
(
int
pipefd[
2
]);
参数:
参数
1
:
int
pipefd[
2
]
:存储⽂件描述符
fd[
0
]
----
read
打开的⽂件描述符
fd[
1
]
----
write
打开的⽂件描述符
C
控制进程的执⾏,是⼀种异步通知
信号是直接⽤于进程和内核之间进⾏交互,内核通过信号通知进程系统产⽣
了什么事情或变化
进程
1
和进程
2
要通过信号进⾏通信,依靠内核信号来实现
信号:
kill -l
查询
信号的发送与捕捉
信号的发送:
信号的捕获处理:
对于信号⽽⾔,具体的信号需要和操作关联
⼀个进程可以设定对信号的响应⽅式
进程对信号的响应⽅式有三种:
5.
信号
#
include
<sys/types.h>
#
include
<signal.h>
给指定的进程发送指定的信号
int
kill
(
pid_t
pid,
int
sig);
参数:
参数
1
:
pid_t
pid
:发送给指定
pid
的进程
参数
2
:
int
sig
:发送的信号
返回值:
成功:返回
0
失败:返回
-
1
C
忽略:对信号不做任何处理
缺省
(
默认
)
:对信号做默认操作
捕获:定义信号处理函数,当产⽣信号后,使⽤对应的函数进⾏处理
默认:
19) SIGSTOP
暂停
20) SIGTSTP
暂停
17) SIGCHLD
忽略
发送定时信号
alarm
#
include
<signal.h>
typedef void
(
*
sighandler_t
)(
int
);
//
类型替换:
sighandler_t
表⽰⼀个函数指针
设定信号的响应功能是什么
设定进程接捕获到信号时如何处理信号
sighandler_t
signal
(
int
signum,
sighandler_t
handler);
这个函数不会阻塞等待信号产⽣,调⽤该函数后,就设定信号的处理⽅
式,只要进程捕获到对应信号就按照设定的⽅式进⾏处理
参数:
参数
1
:
int
signum
:对进程中要捕获的信号
参数
2
:
sighandler_t
handler
:函数指针,
函数指针:函数地址,如果接收对应的捕获信号,采
⽤这个函数指针的对应函数进⾏处理
SIG_IGN
:忽略该信号
(
信号不能是
9
) SIGKILL
19
) SIGSTOP
这两个信号不能忽略
)
SIG_DFL
:使⽤默认的⽅式进⾏处理
(
当接收到信号
后
)
C
C
#
include
<unistd.h>
在指定的时间后,给当前进程发送
SIGALRM
信号
unsigned int
alarm
(
unsigned int
seconds);
阻塞等待信号接收
pause
C
#
include
<unistd.h>
int
pause
(
void
);