文章目录
- 一、进程相关概念
- 1、程序和进程
- 2、并发
- 3、单道程序设计
- 4、多道程序设计
- 5、CPU和MMU
- 6、进程控制块PCB
- 7、进程状态
- 二、环境变量
- 1、常见环境变量
- 1.1 PATH
- 1.2 SHELL
- 1.3 TERM
- 1.4 LANG
- 1.5 HOME
- 2、相关函数
- 2.1 getenv
- 2.2 setenv
- 2.3 unsetenv
- 三、进程控制
- 1、fork
- 2、getpid
- 3、getppid
- 4、getuid
- 5、getgid
- 6、进程共享
- 7、gdb调试
- 三、exec函数
- 1、execlp
- 2、execl
- 四、回收子进程
- 1、孤儿进程
- 2、僵尸进程
- 3、wait函数
- 4、waitpid函数
- 5、案例
- 五、IPC方法
- 1、管道
- 1.1 pipe
- 1.2 案例
- 2、共享存储映射
- 2.1存储映射I/O
- 2.2 mmap
- 2.3 munmap
- 2.4 常见问题
- 2.5 案例
- 2.6 mmap父子进程通信
- 2.7 匿名映射
- 2.8 mmap无血缘关系进程间通信
一、进程相关概念
1、程序和进程
- 程序:编译好的二进制文件;
- 进程:抽象概念,活跃的程序,占用资源,在内存中执行;
- 同一程序可加载为不同的进程;
2、并发
一个时间段中有
多个进程
都处于启动到结束
之间的状态,在任一时刻都只有一个进程
在运行。
3、单道程序设计
一次只能运行一个程序;
4、多道程序设计
- 同时存放几道相互独立的程序;
- 首先必须要有支持该硬件的基础;
- 时钟中断是其基础:强制让进程让出cpu,1秒可执行10亿条指令;
5、CPU和MMU
cpu工作原理
将命令传入到预取器中,后转到译码器进行译码,在传入算数逻辑单元执行,再将处理好的数据传到寄存器堆,最后再传回寄存器;
存储介质
MMU工作原理
- 位于cpu内部;
- 虚拟内存与物理内存的映射;
- 设置修改内存访问级别;
6、进程控制块PCB
每个进程在内核中都有一个PCB来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体;
struct task_struct
- 进程id;
- 状态;
- 进程切换时需要保存和恢复的一些cpu寄存器;
- 描述虚拟地址空间的信息;
- 描述控制终端的信息;
- 当前工作目录;
- umask掩码;
- 文件描述符表,包含很多指向file结构体的指针;
- 和信号相关的信息;
- 用户id和组id;
- 会话和进程组;
- 进程可以使用的资源上限;
7、进程状态
初始态、就绪态、运行态、挂起态、终止态;
二、环境变量
- 在操作系统中用来指定操作系统运行环境的一些参数;
- 位置:位于用户区,高于stack的起始位置;
- 引入环境变量表:需要声明环境变量,
extern char ** environ
- 存储形式:
char *[] environ
,NULL为结尾;
特征
- 字符串;
- 格式:名=值[:值](多个值,则用冒号隔开);
- 值用来描述进程环境信息;
1、常见环境变量
1.1 PATH
- 可执行文件的搜索路径。
- 例:
ls
命令也是一个程序,执行它不需要提供完整的路径名/bin/ls
,然而通常我们执行当前目录下的程序a.out却需要提供完整的路径名/a.out,由于PATH环境变量的值里面包含了Is
命令所在的目录/bin,却不包含a.out所在的目录。PATH 环境变量的值可以包含多个目录,用:
号隔开。- 在Shell中用echo命令可以查看这个环境变量的值:
$ echo $PATH
;
1.2 SHELL
当前shell:
/bin/bash
;
1.3 TERM
1.4 LANG
1.5 HOME
2、相关函数
2.1 getenv
获取环境变量值
char *getenv(const char *name);
char *secure_getenv(const char *name);
/*
* return: 返回一个指向环境中该值的指针,如果不匹配则返回NULL;
*/
2.2 setenv
设置/添加环境变量的值;
int setenv(char *name, const char *value, int overwrite)
/*
* @param overwrite: 1时,覆盖原环境变量,0时,不覆盖;
* return:成功返回0,失败返回-1;
*/
2.3 unsetenv
删除环境变量name的定义
int unsetenv(const char *name)
/*
* return: 成功返回0,失败返回-1,name不存在仍返回0
*/
三、进程控制
1、fork
通过复制创建一个子进程
pid_t fork(void);
/*
* return: 失败返回-1,成功返回:父进程返回子进程的ID以及子进程返回0
*/
2、getpid
返回调用进程的
进程ID
。 (生成唯一临时文件名的例程经常使用此方法)
pid_t getpid(void);
3、getppid
返回调用进程的
父进程
的进程ID。
pid_t getppid(void);
4、getuid
返回调用进程的真实
用户
ID。
uid_t getuid(void);
uid_t geteuid(void);
// 获取当前进程有效id
5、getgid
返回调用进程的真实
组
ID。
gid_t getgid(void);
6、进程共享
父子进程间,遵循
读时共享写时复制
原则;
fork后的相同之处:
.data
;.text
;- 栈;
- 堆;
- 环境变量;
- 用户ID;
- 宿主目录;
- 进程工作目录;
信号处理方式;
相同之处:
- 进程ID;
- fork返回值;
- 父进程ID;
- 进程运行时间;
- 定时器;
- 未决信号集;
共享:
- 文件描述符(打开文件的结构体);
mmap
建立的映射区。
7、gdb调试
gdb
调试只能跟踪一个进程。可在fork函数调用前,通过gdb调试跟踪进程或其子进程,默认跟踪父进程;
set follow-fork-mode child
命令设置gdb在fork之后跟踪父进程;set follow-fork-mode parent
设置跟踪父进程;
三、exec函数
子进程要调用一种exec函数执行另一个程序,并退出该程序;
- l:命令行参数列表;
- p:搜索file时使用path变量;
- v:使用命令参数数组;
- e:使用环境变量数组;
1、execlp
加载一个进程,借助PATH环境变量;
int execlp(const char *file, const char*arg...);
/*
eg: execl('ls', 'ls', '-l', '-a', NULL);
* @param file: 要加载的程序名。配合PATH来使用,当在PATH中所有目录搜索后无该参数则返回-1;
* return: 只在错误的时候返回-1
*/
2、execl
加载一个进程;与
execpl
的差别就在于第一个参数;
int execl(const char *path, const char *arg, ...);
/*
* @param path: 路径+程序名;
* return: 成功无返回,失败返回-1;
*/
四、回收子进程
1、孤儿进程
父进程先于子进程结束,则子进程为孤儿进程,子进程的父进程成为init进程,称为
init
进程领养孤儿进程;
2、僵尸进程
- 进程终止,父进程尚未收回,子进程残留PCB在内核中;
- kill不能终止僵尸进程,因为僵尸进程已终止。
3、wait函数
父进程可调用wait获取,在清除该进程。
- 阻塞等待子进程退出;
- 回收子进程残留资源;
- 获取子进程结束状态(退出原因)。
pid_t wait(int *status);
/*
* @param status: 为传出参数,子进程状态;
* - WIFEXITED为(status)非0 - 进程正常结束;
* - WEXITSTATUS(status) - 上一个宏为真,则获取进程退出状态,exit的参数;
* - WIFSIGNALED(status) - 进程异常终止;
* - WTERMSIG(status) - 上一个宏为真,则取得使进程终止的那个信号的编号;
* return: 返回终止子进程的进程ID; 出错时,返回-1;
*/
4、waitpid函数
作用与
wait
相同,指定pid进程清理,可不阻塞;
pid_t waitpid(pid_t pid, int *status, int options);
/*
* @param pid:
* >0 - 回收指定ID的子进程;
* -1 - 回收任意子进程(=wait);
* 0 - 回收和当前调用waitpid一个组的所有子进程;
* <-1 -回收指定进程组内的任意子进程;
* return: 成功返回清理的子进程,失败返回-1(无子进程),返回0参数3为WONHANG,且子进程正在运行;
*/
5、案例
#include<iostream>
#include<fcntl.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main(){
pid_t pid, wpid;
int status;
//for(int i=0; i<5; i++)
pid = fork();
if(pid == 0){
// 子进程, getppid获取父进程ID
std::cout << "我是子进程,父进程为:" << getppid() << std::endl;
//sleep(20);
exit(1);
}else if(pid == -1){
// 失败
std::cout << "error" << std::endl;
}else{
wpid = wait(&status);
// 异常退出
if(WIFSIGNALED(status) != 0){
std::cout << "子进程的异常退出状态:" << WTERMSIG(status) << std::endl;
}
// 正常退出
if(WIFEXITED(status)){
std::cout << "子进程的正常退出状态:" << WEXITSTATUS(status) << std::endl;
}
while(1){
std::cout << "父进程ID:" << getpid() << "子进程为L: "<< pid << std::endl;
sleep(1);
}
}
return 0;
}
五、IPC方法
Linux下进程是相互
独立
的,不能相互访问
。若要交换数据则需要通过内核
,在内核开辟一块缓冲区
,将进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,该机制为IPC进程间通信
。
常用的通信方式:
- 管道 - 最简单;
- 信号 - 开销最小;
- 共享映射区 - 无血缘关系;
- 本地套接字 - 最稳定。
1、管道
- 其本质是一个
伪文件
(实为内核缓冲区4k);- 由俩个文件描述符引用,一个
读端
,一个写端
;- 规定数据从管道的
写端流入管道,从读端流出
;
局限性:
- 数据自己读
不能自己写
;- 数据被读走,便不在管道中存在,
不可反复读取
;- 由于管道采用
半双工通信方式
。因此,数据只能再一个方向上
流动;- 只能再有
公共祖先
的进程间使用管道;
常用通信方式:
- 单工通信:只能发生信号 - 遥控器;
- 半双工通信:单向发生或接收 - 微信;
- 全双工通信:双向发生接收 - 通话;
1.1 pipe
创建一个管道;
int pipe(int pipefd[2]);
/*
* @param pipefd:
* pipefd[0]指的是管道的读端
* pipefd[1]指到管道的写入端
* return: 成功返回0,失败返回-1
*/
1.2 案例
#include<stdlib.h>
#include<unistd.h>
#include<iostream>
#include<cstring>
/*
* 1、创建子进程,管道
* 2、将子进程设为读、父进程设为写
* 3、通过管道实现进程间通信
* */
int main(int argc, char *argv[]){
pid_t pid;
int fd[2];
int ret = pipe(fd);
if(ret == -1){
std::cout << "pipe create error..." << std::endl;
exit(1);
}
pid = fork();
if(pid == -1){
std::cout << "child process error..." << std::endl;
exit(1);
}else if(pid == 0){ // 子进程
std::cout << "child process id: " << getpid() << std::endl;
close(fd[1]); // 关闭写操作
char buf[1024];
ret = read(fd[0], buf, 1024);
if(ret == -1){
std::cout << "file read error..." << std::endl;
exit(1);
}
// 将读到的数据输出
write(STDOUT_FILENO, buf, ret);
}else{ // 父进程
close(fd[0]); // 关闭读操作
std::string myStr = "test pipe\n";
write(fd[1], myStr.c_str(), myStr.size());
}
return 0;
}
2、共享存储映射
2.1存储映射I/O
- 使一个磁盘文件与存储空间中的一个缓冲区相映射(相当于从缓冲区取数据)。类似于,将数据存入缓冲区,则相应字节就自动写入文件,即使用
I/O地址
操作;- 首先应通知内核,将一个指定文件映射到存储区域,可通过
mmap
函数实现;
2.2 mmap
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
/*
* @param addr: 建立映射区的首地址,有Linux内核指定,使用时,传入NULL即可;
* @param length: 指定映射的长度;
* @param prot:映射区权限;
* - PROT_NONE:
* - PROT_EXEC: 可能被执行;
* - PROT_READ: 读取;
* - PROT_WRITE: 写;
* - PROT_NONE: 无法访问;
* @param flags: 标志位参数(用于设定更新物理区域、设置共享、创建匿名映射区)
* - MAP_SHARED:会将映射区所做的操作反映到物理设备上;
* - MAP_PRIVATE:映射区所做的修改不会反映到物理设备;
* @param fd:用来建设映射区的文件描述符;
* @param offset:映射文件的偏移(4k的整数倍);
* return: 返回一个指向映射区域的指针,失败返回MAP_FAILED;
*/
2.3 munmap
系统调用删除指定地址范围的映射,并导致对在产生
无效内存
引用的范围内的地址。 该区域也会自动取消
映射进程终止。
int munmap(void *addr, size_t length);
/*
* @param addr: map的返回值;
* return: 失败返回0;
*/
2.4 常见问题
- 映射区大小不能为0;
- 创建映射区的权限要 <= 文件打开的权限,映射区的创建隐含着对文件的读操作;
offset
必须是4k的整数倍;- 映射区的释放与文件关闭无关,只要映射
建立成功
,文件即可关闭
;munmap
传入的第一个参数一定是mmap
返回的地址;
2.5 案例
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <cstring>
/*
* 1、打开文件,并获取文件大小
* 2、创建映射区
* 3、
* */
int main(int argc, char *argv[]){
char *p = NULL;
int fd = open("test.txt", O_CREAT|O_RDWR, 0644); // 打开/创建文件
if(fd < 0){
std::cout << "open error..." << std::endl;
exit(1);
}
// 拓展文件大小
int ret = ftruncate(fd, 4);
if(ret == -1){
std::cout << "ftruncate error..." << std::endl;
exit(1);
}
// 创建映射区,注意c++中需要强转
p = (char *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED){
std::cout << "mmap error..." << std::endl;
exit(1);
}
std::cout << "-----------------------" << std::endl;
strcpy(p, "test\n");
ret = munmap(p, 4);
if(ret == -1){
std::cout << "munmap error..." << std::endl;
exit(1);
}
std::cout << "over............." << std::endl;
close(fd);
return 0;
}
2.6 mmap父子进程通信
有
血缘
关系的进程可通过mmap
建立的映射区来完成数据通信,需要设置对应的flag:
MAP_PRIVATE
:(私有映射)父子进程各自独占
映射区;MAP_SHARED
:(共享映射)父子进程共享
映射区;
- 结论:
- 打开的文件;
- mmap建立的映射区,需要用
MAP_SHAPED
案例:
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/mman.h>
int t_val = 1;
/*
*结论:
* 打开的文件;
* mmap建立的映射区,需要用`MAP_SHAPED`
*/
int main(int argc, char *argv[]){
pid_t pid; // 存储fork返回的子进程ID
int fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);
if(fd < 0){
perror("open error");
exit(1);
}
/*
* 删除临时文件目录项,让所有占用该文件的进程都结束后被删除
* */
unlink("temp.txt");
ftruncate(fd, 4);
int *p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED){
perror("mmap error");
exit(1);
}
close(fd); // 映射区建立好即可关闭
pid = fork();
if(pid == 0){
// 子进程返回
*p = 1000;
t_val = 200;
std::cout << "child: " << getpid() << "*p: " << *p << "t_val: " << t_val << std::endl;
}else if(pid < 0){
perror("fork error");
exit(1);
}else{
sleep(1);
std::cout << "parent: " << getppid() << " child: " << pid << " *p: " << *p << " t_val: " << t_val << std::endl;
wait(NULL);
int ret = munmap(p, 4);
if(ret == -1){
perror("munmap error");
exit(1);
}
}
std::cout << "---------------" << std::endl;
return 0;
}
2.7 匿名映射
无需依赖一个文件即可创建映射区,且需要标志位来flag来指定;
- flag只需或上:
MAP_ANONYMOUS/MAP_ANON
;- 文件描述符使用
-1
代替;- 是linux系统特有的;
类UNIX系统中
- 需要借助
fd = open("/dev/zero", O_RDWR)
- 该文件无大小;
2.8 mmap无血缘关系进程间通信
写文件
#include<iostream>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/mman.h>
#include<cstring>
#include<stdio.h>
/*@ 学生结构体 */
typedef struct STU{
int id;
char name[10];
char sex;
}stu;
/* 写文件
*
* */
int main(int argc, char *argv[]){
STU student = {10, "xiaoming", 'm'};
char *mm;
if(argc < 2){
std::cout << "./a.out file_shared" << std::endl;
exit(-1);
}
// 打开文件
int fd = open(argv[1], O_RDWR|O_CREAT, 0644);
ftruncate(fd, sizeof(student));
// 建立映射区
mm = (char *)mmap(NULL, sizeof(student), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(mm == MAP_FAILED){
perror("mmap error");
exit(-1);
}
close(fd);
while(1){
memcpy(mm, &student, sizeof(student));
student.id++;
std::cout << "write..." << std::endl;
sleep(1);
}
munmap(mm, sizeof(student));
return 0;
}
读文件
#include<iostream>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<cstring>
#include<sys/mman.h>
#include<stdio.h>
/*@ 学生结构体 */
typedef struct stu{
int id;
char name[10];
char sex;
}STU;
/* 读文件
*
* */
int main(int argc, char *argv[]){
STU student;
STU *st;
if(argc < 2){
std::cout << "a.out file_sharead\n" << std::endl;
exit(-1);
}
// 只读模式,打开文件
int fd = open(argv[1], O_RDONLY);
if(fd == -1){
perror("open error");
exit(-1);
}
// 建立映射,建立通信
st = (STU *)mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED, fd, 0);
if(st == MAP_FAILED){
perror("mmap error");
exit(-1);
}
close(fd);
// 打印数据
while(1){
std::cout << "id: " << st->id << "name: " << st->name << "性别: " << st->sex << std::endl;
sleep(2);
}
// 终止映射
munmap(st, sizeof(student));
return 0;
}