Linux应⽤层开发

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 );
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值