一、进程相关概念
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);
2.2 setenv
设置/添加环境变量的值;
int setenv(char *name, const char *value, int overwrite)
2.3 unsetenv
删除环境变量name的定义
int unsetenv(const char *name)
三、进程控制
1、fork
通过复制创建一个子进程
pid_t fork(void);
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);
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...);
2、execl
加载一个进程;与execpl 的差别就在于第一个参数;
int execl(const char *path, const char *arg, ...);
四、回收子进程
1、孤儿进程
父进程先于子进程结束,则子进程为孤儿进程,子进程的父进程成为init进程,称为init 进程领养孤儿进程;
2、僵尸进程
- 进程终止,父进程尚未收回,子进程残留PCB在内核中;
- kill不能终止僵尸进程,因为僵尸进程已终止。
3、wait函数
父进程可调用wait获取,在清除该进程。
- 阻塞等待子进程退出;
- 回收子进程残留资源;
- 获取子进程结束状态(退出原因)。
pid_t wait(int *status);
4、waitpid函数
作用与wait 相同,指定pid进程清理,可不阻塞;
pid_t waitpid(pid_t pid, int *status, int options);
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;
pid = fork();
if(pid == 0){
std::cout << "我是子进程,父进程为:" << getppid() << std::endl;
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]);
1.2 案例
#include<stdlib.h>
#include<unistd.h>
#include<iostream>
#include<cstring>
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);
2.3 munmap
系统调用删除指定地址范围的映射,并导致对在产生无效内存 引用的范围内的地址。 该区域也会自动取消 映射进程终止。
int munmap(void *addr, size_t length);
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>
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);
}
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;
int main(int argc, char *argv[]){
pid_t pid;
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;
}
|