linux系统调用进程

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]
进程状态:  初始态、就绪态、运行态、挂起态、终止态

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]
	    	// 末尾的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)){
          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;
}

// 问题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 一个组所有子进程
		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用于没有亲缘关系进程通信
     信号
     共享映射区
     本地套接字

4.1. pipe管道

 4.1.1. 基本用法

     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) 管道已满,阻塞等待
          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;
}

 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;
}

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. 管道写端

#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);
}

4.3. mmap文件映射到内存

功能: 把文件内容映射到内存中返回指针,直接操作指针
  一个写多个读: 存在重复读问题,如果写的还没有覆盖,不会像管道一样读完了就没有了,只能覆盖

【注意】 无血缘关系通信 mmap 和 fifo区别
        mmap 数据可以重复读取,只要没有被覆盖
        fifo 数据只能一次读取

int main017() {
	/*
	 *  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: 私有
fd: 磁盘上文件文件描述符,磁盘上文件映射到共享内存
off_t: 默认0,表示映射文件全部, 如果需要映射部分(偏移位置,必须是4K内存整数倍)

 返回值:
   成功: 映射区的首地址
   失败:MAP_FALED宏,设置errno
	 */
    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

错误类型归纳

/**
 * 错误类型归纳
 * 1. 如果文件大小为0,mmap 函数传递参数大于0 ,“出总线错误"
 * 2. 如果文件大小大于0, mmap 函数传递参数为 0,mmap error: Invalid argument
 * 3. 如果mmap 不为 4096的整数倍,mmap error: Invalid argument
 *  总线错误
 */
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
	// 那么 必须打开文件,可以使用 /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;
}
发布了53 篇原创文章 · 获赞 55 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/dreams_deng/article/details/104199190