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

如何看懂上面这个图:

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 控制块

https://www.cnblogs.com/tongyan2/p/5544887.html    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

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Re: 《Linux 进程管理命令》   ---------------------------------------内容提要: 01/15)命令 ps         :查看进程(快照)02/15)命令 pstree   :显示进程状态树03/15)命令 pgrep   :查找匹配条件的进程04/15)命令 kill        :终止进程号(1277)05/15)命令 killall    :通过进程名(nginx)终止进程(父/子进程)06/15)命令 pkill      :通过进程名终止进程(通杀)/终止客户端(pst/tty)07/15)命令 top       :实时显示系统中各个进程的资源占用状况(录像)08/15)命令 nice      :调整程序运行时的优先级09/15)命令 renice   :调整运行中的进程的优先级10/15)命令 nohup  :用户退出系统进程继续工作11/15)命令 strace   :跟踪进程系统调用12/15)命令 ltrace    :跟踪进程调用库函数13/15)命令 runlevel:输出当前运行级别14/15)命令 init        :初始化 Linux 进程15/15)命令 service  :管理系统服务  本人在教学和实战过程中发现,即便是有一定运维经验的人,可能已经能够搭建一定复杂度的Linux架构,但是在来来回回的具体操作中,还是体现出CLI(命令界面)功底不够扎实,甚至操作的非常‘拙’、处处露‘怯’。 对一个士兵来说,枪就是他的武器,对于一个程序员来说,各种library(工具库)就是他的武器;而对于Linux运维人员来说,无疑命令行工具CLI(命令界面)就是他们的武器;高手和小白之间的差距往往就体现在对于这些“武器”的掌握和熟练程度上。有时候一个参数就能够解决的事情,小白们可能要写一个复杂的Shell脚本才能搞定,这就是对CLI(命令界面)没有理解参悟透彻导致。 研磨每一个命令就是擦拭手中的作战武器,平时不保养不理解,等到作战的时候,一定不能够将手中的武器发挥到最好,所以我们要平心、静气和专注,甘坐冷板凳一段时间,才能练就一身非凡的内功! 本教程从实战出发,结合当下流行或最新的Linux(v6/7/8 版本)同时演示,将命令行结合到解决企业实战问题中来,体现出教学注重实战的务实精神,希望从事或未来从事运维的同学,能够认真仔细的学完Linux核心命令的整套课程。 本课程系列将逐步推出,看看我教学的进度和您学习的步伐,孰占鳌头! 注:关于教学环境搭建,可以参考本人其它课程系列,本教学中就不再赘述! 《参透 VMware 桌面级虚拟化》 《在虚拟机中安装模版机(包括应用软件等)》 《SecureCRT 连接 GNS3/Linux 的安全精密工具》

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值