1. 基础概念
1.程序和进程区别: 进程占用内存、cpu
操作系统进程设置:
单进程序设计:比如早起dos系统,听歌了不能干其他的
多道程序设计单核cpu:cpu时间片切换
多核
2.地址空间 & mmu
程序、命令的运行都会产生进程[比如ls 会产生进程,都是很快终止了该进程]
32位系统:
2^32=4G 虚拟地址
0-3G: 用户空间
3-4G: 内核空间
62位系统:2^64
虚拟地址和物理内存映射关系:
0-3G
int a=100; 在0-3G虚拟内存申请,实际运行中要把虚拟内存映射到物理内存中[内存条]通过MMU,不同程序映射地址不同
3-4G
把不同程序的pcb映射到内存条中
pcb一个结构体,保存进程相关信息[ 进程id、进程状态、文件描述符表、进程工作目录位置、信号相关信息资源、用户id和组id]
进程状态: 初始态、就绪态、运行态、挂起态、终止态
如何看懂上面这个图:
1.上面是虚拟地址4G, 实际空间可能只有 512M
2. 数组在虚拟内存上是连续的, 但是物理内存不一定是
3. a.out 的内核区 和 b.out的内核区 映射到内存条上 是同一块区域 ,进程这样才可以通信
a.out int a b.out int a 映射到内存条是不同空间
MMU功能:
1.内存映射
2. 修改访问级别, 用户空间到内核空间要切换,切换要时间,MMU 可以把用户空间 切换到内核空间
int a= 100 ; 存储在虚拟地址上, 如何映射到 物理内存 , 通过 mmu
2. pcb 控制块
pcb结构体
掌握什么东西:
pid 进程id 运行状态 文件描述符表,
2. LInux进程管理
2.1. ps aux
【 ps aux: a前台进程 x后台进程 u进程产生用户 】
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
PID: pid为1,进程的1号进程/sbin/init,其他的都是它的儿子孙子
VSZ: 占用虚拟内存
RSS: 实际内存
TTY: 进程哪个终端来的
?: 不知道哪个终端来的,内存产生的该进程, 守护进程可能是 ,没有控制终端
**tty1/7代表本地控制终端, tty1-tty6 本地字符终端 tty7图形终端
linux 的终端就是控制台, 是用户与内核交互的平台
Ctrl-Alt-F1 组合键可切换到第一个终端;
Ctrl-Alt-F2 组合键可切换到第一个终端,依次到F6共6个终端,F7为桌面终端。
** pts/0-256代表虚拟终端,比如远程终端,本地打开终端
STAT 进程状态
R: 运行
S: 睡眠
T: 停止
s: 包含子进程
+: 位于后台
START: 进程启动时间
TIME: 占用cpu时间
COMMAND: 进程命令位置
2.2.top命令 【 linux系统管理器】
-d 秒数: 每个几秒更新,默认3s
输入top命令以后交互命令:
shitf+m: 按照内存排序
shift+p: 按照cpu排序
q: 退出
top - 18:02:22 up 7:08, 1 user, load average: 0.00, 0.00, 0.00
系统当前时间 运行时间 登录用户 系统在1分钟,5分钟,15分的平均负载,一般认为小于1时,负载较小,如果大于1,系统以及超出负载
Tasks: 280 total, 3 running, 185 sleeping, 0 stopped, 0 zombie
进程统计
%Cpu(s): 0.0 us, 7.7 sy, 0.0 ni, 92.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
cpu空闲百分比[id]
KiB Mem : 7587384 total, 5053784 free, 1647996 used, 885604 buff/cache
内存
KiB Swap: 998396 total, 998396 free, 0 used. 5602492 avail Mem
交换分区
2.3.pstree命令:查看进程树
-p: 显示进程id
-u: 显示进程用户
2.4. 进程管理kill
kill -l: 进程信号,通过给进程发送信号来控制进程
常用信号:
1) SIGHUP 该信号让进程立即停止
9) SIGKILL 强制终止
15) SIGTERM 正常结束进程,默认,如果无法终止,使用9
例子终止eclipse:
pstree -p|grep java
kill -9 pid
killall -9 进程名[java]
3. linux 进程 api函数
3.1 fork 函数
3.1.0 基本使用
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/mman.h>
// fork 基本使用
int main001(){
printf("before.....\n");
pid_t pid=fork();
// fork原理: 调用一次返回2次, 产生子进程
// 子进程返回0 父进程返回子进程pid
// fork后面的代码,父子进程都会执行,fork前代码父进程执行
// fork 以后父进程还是子进程首先执行,看cpu调用调度算法
if(pid == -1){
perror("fork fail....");
exit(1);
}else if(pid==0){
printf("child process pid %d,parent-pid:%d -- \n",getpid(),getppid());
}else if(pid>0){
// 运行该进程的进程bash
printf("father process,my child is: %d -- father %d \n",pid,getppid());
}
return 0;
}
***** 进程共享: fork以后
父子进程相同: 全局变量、.data、text[代码段]、栈、堆、宿主目录位置、进程工作位置、信息处理方式
不同: 进程id、返回值、进程父进程、闹钟(定时器)、未决定型号集、
问题: 子进程间复制父进程0-3G用户空间内容、以及父进程pcb,但是pid不同,会这样执行吗?
不是, 读时共享、写时候复制,
子进程读父进程变量,共享父进程变量
子进程对父进程变量写,复制一份子进程
3.1.1 如何创建5个子进程
// 如何创建5个子进程
int main002 (){
/**
* i=0以后,会产生 一个子进程1, 那么子进程也持有 for(int i=1;i<5;i++){} 代码
* i=1, 父进程产生子进程2, 子1进程,此时子进程for(int i=2;i<5;i++){} 产生孙子进程孙子1 for(int i=2;i<5;i++){}
* i=2.....
*/
// for(int i=0;i<5;i++){
// pid_t pid=fork();
// if(pid == -1){
// perror("fork fail....");
// exit(1);
// }else if(pid==0){
// printf("child process pid %d,parent-pid:%d -- \n",getpid(),getppid());
// }else if(pid>0){
// // 运行该进程的进程bash
// printf("father process,my child is: %d -- father %d \n",pid,getppid());
// }
// }
int i;
for(i=0;i<5;i++){
// 子进程持有for循环资源,
/**
* for(int i=1;i<5;i++){} 但是break以后,子进程退出
* 走for 下面内容
* 父进程 继续走 for循环 ,知道i==5 走出for 循环
*/
if(fork()==0){
break;
}
}
if(i==5){
printf("father process...%d \n",getpid());
}else {
printf("son process...%d \n",getpid());
}
return 0;
}
for 错误创建子进程
3.2 exec 函数族
exec 函数族( 为什么叫族,因为有多个)
fork 子进程和父进程通过 if 来区分不同代码块执行,子进程如果调用了一种exec函数执行另外一个程序,
子进程的空间代码和数据完全被新程序替换, 当前进程的 text 、data 被需要加载的程序的 .text、.data替换,
但是该进程的进程id不变
int execl(const char* path,const char* arg,....)
int execlp(const char* file,const char* arg,...)
//exec 函数族
int main003 (){
pid_t pid=fork();
if(pid == -1){
perror("fork fail....");
exit(1);
}else if(pid==0){
// 执行系统命令
//int execlp(const char* file,const char* arg,...) 文件名,后面是参数 p表示变量$path
// execlp("ls","ls","-l",NULL);
// 第二个参数 argv[0], 所以必须写上ls这里
// 末尾的NULL表示结尾,哨兵
// 如果执行成功,那么执行ls 不会执行后面
// 如果出错,执行后面
// perror("exec error");
// exit(1);
// 执行本地命令
//int execl(const char* path,const char* arg,....) 路径 执行命令
// 执行本地程序
// execl("./a.o","./a.o","b","c",NULL);
// 练习:把执行结果写入文件中
int fd = open("a.txt", O_WRONLY | O_CREAT | O_TRUNC , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(fd==-1){
perror("open fail");
exit(1);
}
dup2(fd,STDOUT_FILENO);
execl("/bin/ls","ls","-l",NULL);
printf("child process pid %d,parent-pid:%d -- \n",getpid(),getppid());
close(fd);
// char* argv[]={"ls","-l",NULL};
// execvp("ls",argv);
}else if(pid>0){
// 运行该进程的进程bash
printf("father process,my child is: %d -- father %d \n",pid,getppid());
}
return 0;
}
3.3.孤儿进程
父进程先于子进程结束,子进程成为孤儿进程,子进程的父进程成为init进程,init领养孤儿进程
int main004 (){
// 孤儿进程
pid_t pid= fork();
// 父进程睡9s以后死了
// 那么子进程成为孤儿进程
if(pid == -1){
perror("fork fail....");
exit(1);
}else if(pid==0){
while(1){
printf("i am child,my parent id: %d\n",getppid());
sleep(1);
}
}else if(pid>0){
printf("i am parent,my pid is %d\n ", getpid());
sleep(9);
}
return 0;
}
运行程序
9s以后符进程退出
3.4.僵尸进程
进程终止,父进程没有回收,子进程残留在pcb存在内核中,用户空间以及被回收了,僵尸进程
int main005 (){
// 僵尸进程
// 子进程睡9S以后死了
// 父进程一直处于while状态,没有时间来回收子进程,成为了僵尸进程子进程
pid_t pid = fork();
if (pid == -1) {
perror("fork fail....");
exit(1);
} else if (pid == 0) {
printf("child,my parent = %d ", getppid());
sleep(9);
} else if (pid > 0) {
while (1) {
printf("i am father,my pid %d , my son pid %d\n", getpid(),pid);
sleep(1);
}
}
return 0;
}
3.5. 父进程回收子进程wait waitpid
int main006 (){
// wait 函数
pid_t pid = fork();
int status;
pid_t wpid;
if (pid == -1) {
perror("fork fail....");
exit(1);
} else if (pid == 0) {
printf("child,my pid = %d \n ", getpid());
sleep(9);
printf("child game over \n ");
} else if (pid > 0) {
// 结束子进程,会阻塞在这里,直到子进程退出返回wpid
wpid=wait(&status);
if(wpid==-1){
perror("wait error ");
exit(-1);
}
/**
* 判断进程怎么死的: man 2 wait
* status保存进程退出状态,可以借助宏函数来进一步推断具体退出原因
* 宏函数分为三组:
* WIFEXITED 非0 -> 进程正常退出
* WEXITSTATUS 如果上面宏为真,可以使用此宏 -》 获取退出状态
*
* WIFSIGNALED 为真
* 表示该程序被信号终止,异常终止
*/
// 如果子进程是正常退出的, 为真
if(WIFEXITED(status)){
// 退出状态就是main 函数执行完毕以后的值,0 正常
printf("WIFEXITED----%d\n",WEXITSTATUS(status));
}
// 如果子进程非正常退出, 比如kill 被杀掉的,
// 获取杀死子进程的那个信号的值
if(WIFSIGNALED(status)){
// 可以判断哪个信号
printf("kill----%d\n",WTERMSIG(status));
// 使用kill -9 pid 模拟
}
printf("parent game over... %d \n",wpid);
}
return 0;
}
// 问题1: 如果子进程死循环,那么进程wait一直等待,waitpid可以解决,参数3设置成WNOHANG,waitpid不会阻塞,往下走,无法回收子进程
// 问题2: 如果有多个子进程,wait 无法指定具体进程,waitpid第一个参数指定pid wait、waitpid只能回收一个子进程
// 如果返回值 >0 回收子进程成功
// 0 参数3指定WNOHANG, 并且没有子进程被回收
// -1 失败 errno
int main007 (){
// waitpid 函数
pid_t pid = fork();
int status;
pid_t wpid;
if (pid == -1) {
perror("fork fail....");
exit(1);
} else if (pid == 0) {
printf("child,my pid = %d \n ", getpid());
sleep(9);
printf("child game over \n ");
} else if (pid > 0) {
// pid == -1 回收任意子进程,功能等于wait
// 必须把sleep放在前面,如果子进程还在sleep,父进程调用waitpid那么 返回0 无法回收
// 要回收,必须等子进程执行完毕了以后才调用该函数才有效果
// pid > 0 回收指定id子进程
// pid = 0 回收当前调用 waitpid 一个组所有子进程
// 第三个参数如果写0 ,功能等于wait ,阻塞
// WNOHANG 非阻塞
sleep(19);
wpid=waitpid(pid,&status,WNOHANG);
if(wpid==-1){
perror("wait error ");
exit(-1);
}
/**
* 判断进程怎么死的: man 2 wait
* status保存进程退出状态,可以借助宏函数来进一步推断具体退出原因
* 宏函数分为三组:
* WIFEXITED 非0 -> 进程正常退出
* WEXITSTATUS 如果上面宏为真,可以使用此宏 -》 获取退出状态
*
* WIFSIGNALED 为真
* 表示该程序被信号终止,异常终止
*/
if(WIFEXITED(status)){
printf("WIFEXITED----%d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status)){
// 可以判断哪个信号
printf("kill----%d\n",WTERMSIG(status));
// 使用kill -9 pid 模拟
}
printf("parent game over... %d \n",wpid);
}
return 0;
}
案例回收多个进程
/**
* 回收多个进程
*/
int main008 (){
int i;
pid_t wpid;
for(i=0;i<5;i++){
if(fork()==0){
break;
}
}
if(i==5){
printf("father process...%d \n",getpid());
// 这里必须要使用死循环
while( (wpid = waitpid(-1,NULL,WNOHANG))!=-1){
if(wpid >0){
printf("wait child... %d\n",wpid);
}else{
sleep(1);
continue;
}
}
}else {
printf("son process...%d \n",getpid());
}
return 0;
}
4. linux进程之间通信
进程通信(IPC):
原理:3G-4G内核 所有进程是共享的, 在内存中有一块共享缓冲区4096,用于读写
管道: pipe管道只能用于亲缘进程通信 fifo用于没有亲缘关系进程通信血缘关系: 比如父子,兄弟,通信共同点,文件描述符
信号: 开销小
共享映射区 : 没有血缘关系mmap
本地套接字 : 网络
4.1. pipe管道
4.1.1. 基本用法
mkfifo f1 : 创建管道用命令, linux 下有7中文件类型,
文件,软连接,目录都是占用磁盘的,其他的都是伪文件,不用占用磁盘空间,比如管道
pipe函数:创建、打开管道
int pipe(int fd[2])
参数: fd[0]: 读端
fd[1]: 写端
返回值: 成功:0
失败:-1
1.必须作用在有血缘关系的父子进程之间,管道是伪文件,不占磁盘,实际上是内核中的一块缓冲
2.管道使用环形队列实现,一端写、一端读,数据不能进程自己写、自己读
3. 管道数据不能重复读取,一旦读走,管道中不存在
4. 采用双向半双工通信,数据只能单方向流动
单工: 只能从A->B
双向半双工: 比如对讲机,一个说一个只能听不能2个人同时说,可以从A->B 也可以从B->A 但是不同同时
双向全双工: 手机可以同时说实现原理: 内部实现就是环形队列机制, 所以不能自己读,自己写,导致上面的 特征
int main009(){
/**
pipe函数:创建、打开管道
int pipe(int fd[2])
参数: fd[0]: 读端
fd[1]: 写端
返回值: 成功:0
失败:-1
数组里面的内容是文件描述符
打开管道以后,父进程对 pipe 管道, 有读写在两端, 父进程写,那么关闭读端
打开管道以后,子进程对 pipe 管道, 有读写在两端, 子进程写,那么关闭读端
*/
int fd[2];
int ret=pipe(fd);
if(ret== -1){
perror("pipe error");
exit(1);
}
pid_t pid=fork();
char buf[1024]={0};
if(pid == -1){
perror("fork fail....");
exit(1);
}else if(pid==0){
close(fd[1]);
read(fd[0],buf,sizeof(buf));
printf("read context.....%s\n",buf);
close(fd[0]);
//printf("child process pid %d,parent-pid:%d -- \n",getpid(),getppid());
}else if(pid>0){
// 父进程关闭写端数据
char * str="hello world pipe \n";
close(fd[0]);
write(fd[1],str,strlen(str));
close(fd[1]);
// 运行该进程的进程bash
printf("father process,my child is: %d -- father %d \n",pid,getppid());
}
return 0;
}
4.1.2 管道的读写行为:
读管道:
1.管道有数据,read返回实际读到字节数
2.管道无数据: 1). 无写端把持管道, read返回0(类似读到文件末尾)
2). 有写端,read阻塞等待
写管道
1. 无读端,异常终止(发送了SIGPIPE信号), 管子一样,不断的流入数,管子会爆掉
2. 有读端
1) 管道已满,write阻塞等待. 如果要演示现象,读端一直不读,写一直写
2) 管道未满,返回写出字节个数
/***
* 管道的读写行为:
读管道:
1.管道有数据,read返回实际读到字节数
2.管道无数据: 1). 无写端把持管道, read返回0(类似读到文件末尾)
2). 有写端,read阻塞等待
写管道
1. 无读端,异常终止(发送了SIGPIPE信号)
2. 有读端
1) 管道已满,阻塞等待
2) 管道未满,返回写出字节个数
*/
int main010(){
int fd[2];
int ret=pipe(fd);
if(ret== -1){
perror("pipe error");
exit(1);
}
pid_t pid=fork();
char buf[1024]={0};
if(pid == -1){
perror("fork fail....");
exit(1);
}else if(pid==0){
close(fd[1]);
read(fd[0],buf,sizeof(buf));
printf("read context.....%s\n",buf);
close(fd[0]);
//printf("child process pid %d,parent-pid:%d -- \n",getpid(),getppid());
}else if(pid>0){
// 父进程关闭写端数据
char * str="hello world pipe \n";
close(fd[0]);
/**
* 读管道:
2.管道无数据:
2). 在sleep(10)内有写端把持管道,read阻塞等待
*/
sleep(10);
write(fd[1],str,strlen(str));
close(fd[1]);
// 运行该进程的进程bash
printf("father process,my child is: %d -- father %d \n",pid,getppid());
}
return 0;
}
无读端,异常终止(发送了SIGPIPE信号), 管子一样,不断的流入数,管子会爆掉 , 验证代码如下:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/mman.h>
/***
* 可以多个写端一读端
*/
int main() {
int fd[2];
int ret=pipe(fd);
if(ret== -1){
perror("pipe error");
exit(1);
}
pid_t pid=fork();
char buf[1024]={0};
char * str="hello world pipe \n";
if(pid == -1){
perror("fork fail....");
exit(1);
}else if(pid==0){
sleep(3);
close(fd[0]);//关闭读
write(fd[1],str,strlen(str));
close(fd[1]);
while(1){
sleep(1);
}
//printf("child process pid %d,parent-pid:%d -- \n",getpid(),getppid());
}else if(pid>0){
close(fd[1]); // 父进程写
close(fd[0]); // 关闭读写, 子进程只有写,报错
int status;
wait(&status); //如果要捕获子进程问题,只有父进程回收子进程
// 子进程被13号信号杀死,必须回收,不回收变成僵尸进程
//
if(WIFSIGNALED(status)){ // 判断子进程死亡原因
printf("kill----%d\n",WTERMSIG(status));
}
while(1){
sleep(1);
}
}
return 0;
}
如果套接字坏了,也是这个13
4.1.3 实现功能: ls -l | wc -l
4.1.3.1.实现功能: ls -l | wc -l
// 实现功能: ls -l | wc -l
/**
* 问题:父进程执行完毕,那么bash进程认为他的儿子执行完毕了,抢占终端
* 子进程 后执行完毕,所以输出内容到外面去了
*/
int main011() {
int fd[2];
int ret = pipe(fd);
if (ret == -1) {
perror("pipe error");
exit(1);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork fail....");
exit(1);
} else if (pid == 0) {
// 运行该进程的进程bash
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("wc", "wc", "-l", NULL);
// printf("child process pid %d,parent-pid:%d -- \n", getpid(), getppid());
} else if (pid > 0) {
// 子进程,写
close(fd[0]); //关闭读
dup2(fd[1], STDOUT_FILENO); // 重定向输入写入管道
sleep(5);
execlp("ls", "ls", "-l", NULL); // 执行命令开始写
// printf("father process,my child is: %d -- father %d \n", pid,getppid());
}
return 0;
}
要形成管道流,必须关闭一端对于一个进程
4.1.3.2. 问题:父进程执行完毕,那么bash进程认为他的儿子执行完毕了,抢占终端
解决:把读放入父进程中,那么阻塞,父进程不退出.写放入子进程
/***
* 解决方法:
* 把读放入父进程中,那么阻塞,父进程不退出
* 写放入子进程
*/
int main012(){
int fd[2];
int ret = pipe(fd);
if (ret == -1) {
printf("pipe error");
exit(1);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork fail....");
exit(1);
} else if (pid == 0) {
// 子进程,写
close(fd[0]); //关闭读
dup2(fd[1], STDOUT_FILENO); // 重定向输入写入管道
execlp("ls","ls","-l",NULL); // 执行命令开始写
// printf("child process pid %d,parent-pid:%d -- \n", getpid(), getppid());
} else if (pid > 0) {
// 运行该进程的进程bash
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("wc","wc","-l",NULL);
// printf("father process,my child is: %d -- father %d \n", pid,getppid());
}
return 0;
}
4.1.3.2.兄弟进程实现 ls -l | wc -l
/**
* 兄弟进程实现 ls -l | wc -l
*/
int main013() {
int i;
pid_t pid;
int fd[2];
int ret = pipe(fd);
if (ret == -1) {
printf("pipe error");
exit(1);
}
for (i = 0; i < 2; i++) {
pid = fork();
if (pid == -1) {
perror("fork error");
exit(-1);
} else if (pid == 0) {
// 子进程
break;
}
}
// 子进程1
if (i == 0) {
// 子进程,写
close(fd[0]); //关闭读
dup2(fd[1], STDOUT_FILENO); // 重定向输入写入管道
execlp("ls", "ls", "-l", NULL); // 执行命令开始写
perror("execlp error");
exit(0);
} else if (i == 1) {
// 子进程2
// 运行该进程的进程bash
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("wc", "wc", "-l", NULL);
perror("execlp error");
exit(0);
} else if (i == 2) {
// 父进程,必须把父进程持有管道w和r关闭,否则无法形成环形队列,达到一端读一端写功能
close(fd[0]);
close(fd[1]);
wait(NULL);
wait(NULL);
}
return 0;
}
4.1.4 可以多个写端一读端
支持多端读、多端写,但是不能控制先后顺序
/***
* 可以多个写端一读端
*/
int main014() {
int i;
pid_t pid;
int fd[2];
int ret = pipe(fd);
if (ret == -1) {
printf("pipe error");
exit(1);
}
for (i = 0; i < 2; i++) {
pid = fork();
if (pid == -1) {
perror("fork error");
exit(-1);
} else if (pid == 0) {
// 子进程
break;
}
}
// 子进程1
if (i == 0) {
// 子进程1,写
close(fd[0]);
write(fd[1],"1.hello\n",strlen("1.hello\n"));
} else if (i == 1) {
// 子进程2,写
close(fd[0]);
write(fd[1],"2.hello\n",strlen("2.hello\n"));
} else if (i == 2) {
close(fd[1]);
sleep(1);
char buf[1024]={0};
read(fd[0],buf,sizeof(buf));
printf("%s\n",buf);
wait(NULL);
wait(NULL);
}
return 0;
}
ulimit -a 用来显示当前的各种用户进程限制
管道缓冲区大小, 8个 512 byte 4K 也可以通过 long fpathconf(int fd,int name)函数获取
管道容量,比管道大小要大 ,写数据是容量上面
缺点: 只能用于有血缘关系的进程通信, 只能单进程通信
比如 给父进程给进程发,如果需要子进程给父发, 那么还需要一根管道
4.2. FIFO(有名管道)
可以用于没有亲缘关系进程通信
linux命令创建:mkfifo myfifo
linux函数创建:
mkfifo(const char *pathname, mode_t mode);*** 使用普通文件也可以完成IPC通信,fork以后不同进程共享文件描述符,但是必须一个写入以后才可以读到内容
1. 创建管道
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
int main() {
//mkfifo(const char *pathname, mode_t mode);
// 文件名 mode&umask
// fifo是具体文件了
int ret=mkfifo("mytestfifo",0644);
if(ret == -1){
perror("mkfifo fail");
exit(-1);
}
return 0;
}
2. 管道写端 mkfifo mytestfifo 首先
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
int main() {
int fd=open("mytestfifo",O_WRONLY,0644);
if(fd == -1){
perror("open file faile....");
exit(-1);
}
char ch[4096]={0};
int i=0;
while(1){
sprintf(ch,"abc--%d\n",i++);
write(fd,ch,sizeof(ch));
usleep(1000); // 1s中
}
close(fd);
return 0;
}
3. 管道读端
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
int main() {
int fd=open("mytestfifo",O_RDONLY,0644);
if(fd == -1){
perror("open file faile....");
exit(-1);
}
char ch[4096]={0};
int len=0;
while(1){
len=read(fd,ch,sizeof(ch));
write(STDOUT_FILENO,ch,len);
sleep(3);
}
close(fd);
}
实现方式就是队列,一个写,也可以多个读,可以多个读,读走了就没有了 ,
open注意事项: 打开fifo文件的时候,read端会阻塞等待write端open, write端同理,也会阻塞等待另外一端打开
int main() {
printf("%s\n", "open..begin"); //
int fd=open("mytestfifo",O_RDONLY,0644); // 首先执行写,会阻塞在这里
printf("%s\n","open..end");
if(fd == -1){
perror("open file faile....");
exit(-1);
}
char ch[4096]={0};
int len=0;
while(1){
len=read(fd,ch,sizeof(ch));
write(STDOUT_FILENO,ch,len);
sleep(3);
}
close(fd);
}
4.3. mmap文件映射到内存
功能: 把文件内容映射到内存中返回指针,直接操作指针
一个写多个读: 存在重复读问题,如果写的还没有覆盖,不会像管道一样读完了就没有了,只能覆盖【注意】 无血缘关系通信 mmap 和 fifo区别
mmap 数据可以重复读取,只要没有被覆盖
fifo 数据只能一次读取
int main017() {
/* 1.创建
* void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
addr: 指定映射区首地址,通常传NULL,表示系统自动分配
length: 共享内存大小,(小于等于文件实际大小一般)
prot:表示共享映射区的读写属性
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE Pages may not be accessed.
flags: 标注共享内存的共享属性
MAP_SHARED : 共享,但内存被修改了可以直接同步磁盘
MAP_PRIVATE: 私有, 如果进程要通信 必须设置share,否则无法通信
fd: 磁盘上文件文件描述符,磁盘上文件映射到共享内存
off_t: 默认0,表示映射文件全部, 如果需要映射部分(偏移位置,必须是4K内存整数倍)
返回值:
成功: 映射区的首地址
失败:MAP_FALED宏,设置errno
2. 释放: munmap 内存
*/
char *p=NULL;
int fd;
// 指定文件权限
fd= open("text.map",O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd == -1){
perror("crate file error");
exit(-1);
}
// 扩展文件大小
// 打开一个文件指针在开头,开头就是结尾
// lseek(fd,10,SEEK_END);
// write(fd,"\0",1);
// 上面可以使用ftruncate替代
// 如何查看空洞文件内容:
//od -c file 查看文件存储的内容
// 用于mmap的文件必须要拥有大小,否在出总线错误
ftruncate(fd,10);
// 测量文件长度
int len= lseek(fd,0,SEEK_END);
printf("filesize.....%d\n",len);
// 指定 内存 权限 PROT_READ |PROT_WRITE
p=mmap(NULL,len,PROT_READ |PROT_WRITE,MAP_SHARED,fd,0);
if(p==MAP_FAILED){
perror("mmap error");
exit(-1);
}
// 使用p对文件进行读写
strcpy(p,"hello"); // 写
printf("---%s\n",p);
// 释放内存
int ret= munmap(p,len);
if(ret == -1){
perror("munmap error");
exit(-1);
}
return 0;
}
空洞文件查看:od -c file
错误类型归纳
/**
* 错误类型归纳
0. 如果改变men变量地址,释放munmap释放失败
* 1. 如果文件大小为0,mmap 函数传递参数大于0 ,“出总线错误"
* 2. 如果文件大小大于0, mmap 函数传递参数为 0,mmap error: Invalid argument
* 3. 如果mmap 不为 4096的整数倍,mmap error: Invalid argument
* 总线错误
4.
* 如果文件只有2个字节,传递8个字节,strcpy100个字节,最终文件只有2个字节写进去,不推荐这种用法
* 5. 如果文件描述符先关闭,对 mmap 映射有影响吗?
没有:映射完毕了, 通道打通了, 可以首先关闭
*
*
*/
int main018() {
char *p=NULL;
int fd;
fd= open("text.map",O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd == -1){
perror("crate file error");
exit(-1);
}
// ftruncate(fd,10);
// 测量文件长度
int len= lseek(fd,0,SEEK_END);
printf("filesize.....%d\n",len);
p=mmap(NULL,20,PROT_READ |PROT_WRITE,MAP_SHARED,fd,0);
if(p==MAP_FAILED){
perror("mmap error");
exit(-1);
}
strcpy(p,"hello"); // 写
printf("---%s\n",p);
int ret= munmap(p,len);
if(ret == -1){
perror("munmap error");
exit(-1);
}
return 0;
}
使用mmap进行父子进程通信
/***
* 父子进程通信
*/
int main019() {
int *p = NULL;
int fd;
// 指定文件权限
fd = open("text1.map", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("crate file error");
exit(-1);
}
ftruncate(fd, 10);
int len = lseek(fd, 0, SEEK_END);
printf("filesize.....%d\n", len);
// 返回 (void*),用于存储int
p = (int*) mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
perror("mmap error");
exit(-1);
}
// mmap以后就可以关闭文件描述符了
close(fd);
pid_t pid = fork();
if (pid == -1) {
perror("fork fail...");
exit(-1);
} else if (pid == 0) {
//子进程
printf("read context....%d", *p);
} else if (pid > 0) {
// 父进程
*p = 1000;
wait(NULL);
// 释放内存记得
int ret = munmap(p, len);
if (ret == -1) {
perror("munmap error");
exit(-1);
}
}
return 0;
}
使用mmap不同进程通信操作结构体
write.c写端:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/mman.h>
struct Student{
int id;
char name[256];
int age;
};
int main() {
struct Student *p ;
int fd;
struct Student stu={10,"xiaoming",18};
// 指定文件权限
fd = open("text2.map", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("crate file error");
exit(-1);
}
ftruncate(fd, sizeof(stu));
// 返回 (void*),用于存储int
p = mmap(NULL, sizeof(stu), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
perror("mmap error");
exit(-1);
}
close(fd);
while(1){
// 循环写,后面的会把前面的给覆盖
memcpy(p,&stu,sizeof(stu));
stu.id++;
printf("xieru--%d--%s\n",stu.id,stu.name);
sleep(1);
}
munmap(p,sizeof(stu));
return 0;
}
read.c 读端:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/mman.h>
struct Student{
int id;
char name[256];
int age;
};
int main() {
struct Student *p ;
struct Student stu ;
int fd;
// 指定文件权限
fd = open("text2.map", O_RDONLY);
if (fd == -1) {
perror("crate file error");
exit(-1);
}
// 返回 (void*),用于存储int
p = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
perror("mmap error");
exit(-1);
}
close(fd);
while(1){
// 不用调用 read函数 ,直接取就行了,取完毕了就没有了
printf("id=%d,name=%s,age=%d\n",p->age,p->name,p->id);
sleep(1);
}
munmap(p,sizeof(stu));
return 0;
}
write2.c写端多个写端:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/mman.h>
struct Student{
int id;
char name[256];
int age;
};
int main() {
struct Student *p ;
int fd;
struct Student stu={10,"xiaoming",18};
// 指定文件权限
fd = open("text2.map", O_RDWR, 0644);
if (fd == -1) {
perror("crate file error");
exit(-1);
}
ftruncate(fd, sizeof(stu));
// 返回 (void*),用于存储int
p = mmap(NULL, sizeof(stu), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
perror("mmap error");
exit(-1);
}
close(fd);
while(1){
memcpy(p,&stu,sizeof(stu));
stu.id++;
printf("xieru--%d--%s\n",stu.id,stu.name);
sleep(1);
}
munmap(p,sizeof(stu));
return 0;
}
父子进程通信优化,你们映射:
/***
* 父子进程通信优化:
* 匿名映射: 只能用于有关系进程
* fork共享文件描述符,共享mmap映射区
*/
int main022() {
int *p = NULL;
// 优化1: mmap以后就可以关闭文件描述符了
// int fd;
// // 指定文件权限
// fd = open("text1.map", O_RDWR | O_CREAT | O_TRUNC, 0644);
// if (fd == -1) {
// perror("crate file error");
// exit(-1);
// }
// ftruncate(fd, 10);
// int len = lseek(fd, 0, SEEK_END);
// printf("filesize.....%d\n", len);
// // 返回 (void*),用于存储int
// p = (int*) mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// if (p == MAP_FAILED) {
// perror("mmap error");
// exit(-1);
// }
// // mmap以后就可以关闭文件描述符了
// close(fd);
// // 优化1:
// // 文件的作用就是创建缓存区大小,创建好了可以删除
// // 等所有进程都释放该文件了,才删除文件,不是调用了立刻删除
// int ret2=unlink("text1.map");
// if(ret2 == -1){
// perror("unlink error");
// exit(-1);
// }
// 不创建文件
// 优化2: 使用匿名映射,文件大小想要多少传递多少
// 问题: 如果unix不支持MAP_ANONYMOUS , 没有这个宏
// 如果要使用,不创建文件,使用下面这2个文件来解决
// 那么 必须打开文件,可以使用 /dev/zero文件 操作系统的文件
// 这个文件是一个巨大空洞文件,这样也不用创建文件
// 对应的是/dev/null 可以往里面写,想写多少就有多少 , 不如报错的日志
p = (int*) mmap(NULL, 40, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED) {
perror("mmap error");
exit(-1);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork fail...");
exit(-1);
} else if (pid == 0) {
//子进程
printf("read context....%d", *p);
} else if (pid > 0) {
// 父进程
*p = 1000;
wait(NULL);
// 释放内存记得
int ret = munmap(p, 40);
if (ret == -1) {
perror("munmap error");
exit(-1);
}
}
return 0;
}
原理mmap :
mmap 多个进程 拷贝文件
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
// 文件太大了不用考虑
int main(int argc,char* argv[]) {
int n = 5;
// 输入参数 至少是3 第4个参数进程个数
if(argc < 3){
printf("%s\n", "src dst no ");
return 0;
}
if(argc == 4){
n= atoi(argv[3]);
}
// 打开资源文件
int srcfd= open(argv[1],O_RDONLY);
if(srcfd<0){
perror("open err");
exit(1);
}
//打开目标文件
int dstfd = open(argv[2],O_RDWR | O_CREAT | O_TRUNC , 0664);
if(dstfd<0){
perror("open err");
exit(1);
}
// 从源文件获取文件大小, stat
struct stat sb;
stat(argv[1],&sb);
int len = sb.st_size;
truncate(argv[2],len);
// 源文件映射到缓冲区
char* psrc = mmap(NULL,len,PROT_READ,MAP_SHARED,srcfd,0);
if(psrc ==MAP_FAILED ){
perror("mmap src error");
exit(1);
}
//把目标文件映射为文件
char* pdst = mmap(NULL,len,PROT_READ | PROT_WRITE ,MAP_SHARED,dstfd,0);
if(pdst == MAP_FAILED){
perror("mmap src error");
exit(1);
}
// 创建多个子进程用于拷贝
int i=0;
for(i=0;i<n;i++){
if(fork()==0){
break;
}
}
// 计算子进程需要拷贝的起点和大小 文件大小/子进程个数
int cpusize = len/n ;
int mod = len % n ;
//数据拷贝
if(i < n){
if(i == n-1){ // 最后一个进程 拷贝
memcpy(pdst+i*cpusize,psrc+i*cpusize,cpusize+mod);
}else{ // 前几个进程拷贝内容
memcpy(pdst+i*cpusize,psrc+i*cpusize,cpusize);
}
}else{
for(i=0;i<n ;i++){
wait(NULL);
}
}
//释放映射区
if(munmap(psrc,len)<0){
perror("munmap src error");
exit(1);
}
if(munmap(pdst,len)<0){
perror("munmap dst error");
exit(1);
}
// 关闭文件描述符
close(srcfd);
close(dstfd);
return 0;
}
执行命令: ./test.o src.zip dst.zip 4