• Linux系统编程_进程:C程序空间分配、fork、wait、exec、system、popen


    1. 进程相关概念(414.1)

    问1. 什么是程序,什么是进程,有什么区别?

    • 程序是静态的概念,gcc xxx.c –o pro,磁盘中生成pro文件,叫做程序
    • 进程是程序的一次运行活动,通俗点即程序跑起来了,系统中就多了一个进程

    问2. 如何查看系统中有哪些进程?

    • a. 使用ps指令查看进程,实际工作中配合grep来查找程序中是否存在某一进程
    ps// 仅显示当前终端会话中运行的进程的快照
    ps aux// 仅显示当前终端会话中运行的进程的快照
    ps aux|grep xxx//显示包含特定进程名称xxx的进程。
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    在这里插入图片描述

    • b. 使用top指令查看进程实时情况,类似windows任务管理器
      在这里插入图片描述

    问3. 什么是进程标识符?

    • 每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证
      • Pid=0:称为交换进程(swapper),作用—进程调度
      • Pid=1:init进程,作用—系统初始化
    • 编程调用 getpid 函数获取自身的进程标识符,getppid 获取父进程的进程标识符
    • PRO/demo1.c
    #include 
    #include 
    #include 
    
    int main(){
    	pid_t pid;
    
    	pid = getpid();//获取自身的进程标识符
    
    	printf("my pid is %d\n",pid);
    
    	while(1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    问4. 什么叫父进程,什么叫子进程

    • 进程A创建了进程B:
      • 那么A叫做父进程,B叫做子进程
      • 父子进程是相对的概念,理解为人类中的父子关系

    问5. C程序的存储空间是如何分配?(*面试会问)

    在这里插入图片描述

    在这里插入图片描述

    2. 创建进程函数fork的使用(415.2)

    使用fork函数创建一个进程:pid_t fork(void);

    • fork函数调用成功,返回两次
      • 返回值为 0, 代表当前进程是子进程
      • 返回值 非负数(>0),代表当前进程为父进程
      • 调用失败,返回 -1
    • 每一次a.out之后就是当前进程结束之后,重新a.out就是新的进程,so每个pid号不一样
      在这里插入图片描述
    • FILE/demo3.c
      在这里插入图片描述
    • FILE/demo4.c
    #include 
    #include 
    #include 
    
    int main(){
    	pid_t pid;
    	pid_t pid2;
    
    	pid = getpid();
    	printf("before fork: pid = %d\n\n",pid );	
    
    	fork();
    	
    	pid2 = getpid();
    	printf("after fork: pid = %d\n",pid2 );		
    
    	if(pid == pid2){
    		printf("this is father print,pid = %d\n\n",pid);fork创建之前的父进程id
    	}
    	else{
    		printf("this is child print,child pid = %d\n",getpid());//fork创建之后的子进程id
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    • FILE/demo5.c
    #include 
    #include 
    #include 
    
    int main(){
    	pid_t pid;
    
    	printf("father: id =%d\n",getpid());
    
    	pid = fork();
    		
    	if(pid > 0){
    		printf("this is father print,pid = %d\n",getpid());
    		
    	}
    	
    	else if(pid == 0){
    		printf("this is child print,pid = %d\n",getpid());
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3. 创建进程函数fork的使用补充(416.3)

    • FILE/demo6.c(fork的其一非负返回值表父进程,等于子进程pid号)
      在这里插入图片描述

    4. 进程创建发生了什么事(417.4)

    在这里插入图片描述

    旧版本Linux的拷贝:全拷贝

    新版本Linux的拷贝:写实拷贝(Copy-On-Write,COW)(*面试会问)

    • 父子进程在初始阶段共享所有的数据(全局、 栈区、 堆区、 代码), 内核会将所有的区域设置为只读。 当父子进程中任意一个进程试图修改其中的数据时, 内核才会将要修改的数据所在的区域(页) 拷贝一份。(而陈立晨讲的是,仅数据段共享,其余全拷贝,仅当数据段发生修改时才拷贝且独立分配空间)

    • 共享数据
      在这里插入图片描述

    • 写时拷贝后:
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    • FILE/demo7.c

    #include 
    #include 
    #include 
    
    int main(){
    	pid_t pid;
    	int data = 10;
    
    	printf("father: id =%d\n",getpid());
    
    	pid = fork();
    		
    	if(pid > 0){
    		printf("this is father print,pid = %d\n",getpid());
    		
    	}
    	
    	else if(pid == 0){
    		printf("this is child print,pid = %d\n",getpid());
    		data = data+100;
    	}
    	
    	printf("data=%d\n",data);
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    5. 创建新进程的实际应用场景及fork总结(418.5)

    fork创建一个子进程的一般目的

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    fork编程实战

    • PRO/demo8.c
    #include 
    #include 
    #include 
    
    int main(){
    	pid_t pid;
    	int data = 10;
    
    	while(1){
    
    		printf("please input a data\n");
    		scanf("%d",&data);	
    		if(data == 1){
    						
    				pid = fork();
    					
    				if(pid > 0){
    					
    				}
    				else if(pid == 0){
    				
    					while(1){
    						printf("do net request,pid=%d\n",getpid());
    						sleep(3);//使程序休眠3秒
    					}
    				}
    		}
    		else{
    			printf("wait ,do nothing\n");
    		}
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    6. vfork创建进程(419.6)

    在这里插入图片描述

    • vfork 与 fork 区别
      • vfork 直接使用父进程存储空间,不拷贝。
      • vfork 保证子进程先运行,当子进程调用exit退出后,父进程才执行。
    • PRO/demo9.c、demo10.c
    #include 
    #include 
    #include 
    
    int main(){
    	pid_t pid;
    
    	pid = fork();
    	//pid = vfork();
    		
    	if(pid > 0){
    		while(1){
    			printf("this is father print, pid = %d\n",getpid());
    			sleep(3);
    		}	
    	}
    	else if(pid == 0){
    		while(1){
    			printf("this is chilid print, pid = %d\n",getpid());
    			sleep(3);
    		}	
    	}
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • PRO/demo11.c、demo12.c
    #include 
    #include 
    #include 
    #include 
    #include //exit函数的
    
    int main(){
    	pid_t pid;
    
    	int cnt = 0;
    	
    	pid = vfork();
    		
    	if(pid > 0){
    		while(1){
    			printf("cnt=%d\n",cnt);
    			printf("this is father print, pid = %d\n",getpid());
    			sleep(1);
    		}	
    	}
    	else if(pid == 0){
    		while(1){
    			printf("this is chilid print, pid = %d\n",getpid());
    			sleep(1);
    			cnt++;
    			if(cnt == 3){
    				exit(0);
    				//_exit(0);
    				//_Exit(0);
    			}
    		}	
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    7. 进程退出(420.7)

    • 正常退出

      • Main函数调用return
      • 进程调用exit(),标准c库
      • 进程调用_exit()或者_Exit(),属于系统调用
      • 进程最后一个线程返回
      • 最后一个线程调用pthread_exit
    • 异常退出

      • 调用abort
      • 当进程收到某些信号时,如ctrl+C
      • 最后一个线程对取消(cancellation)请求做出响应

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    8. 父进程等待子进程退出 (一)(421.8)

    为啥要等待子进程退出

    在这里插入图片描述

    父进程等待子进程退出并收集子进程的退出状态

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    • status参数:是一个整型数指针
      • 非空:子进程退出状态放在它所指向的地址中
      • 空:不关心退出状态
    • PRO/demo14.c
      在这里插入图片描述
    • PRO/demo15.c
      在这里插入图片描述

    子进程退出状态不被收集,变成僵死进程(僵尸进程)

    • PRO/demo12.c(同demo11)(vfork,子进程退出后再父进程,子进程Z+僵尸进程)
    • PRO/demo13.c(fork无wait收集,子进程为僵尸进程,且父子进程先是交替运行)
      在这里插入图片描述

    9. 父进程等待子进程退出 (二)(422.9)

    waitpid

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    • PRO/demo16.c(waitpid(pid,&status,WNOHANG);//不常用,不挂起不等待退出)
      在这里插入图片描述

    孤儿进程

    • 父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程
    • Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程(pid=1)
    • PRO/demo17.c
      在这里插入图片描述

    unix环境高级编程

    • 代码(子进程退出的状态判断)

    在这里插入图片描述
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
    在这里插入图片描述

    10. exec族函数(423.10)

    • 精彩博文
      • https://blog.csdn.net/u014530704/article/details/73848573

    为什么要用exec族函数,有什么作用

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    • PRO/echoarg.c(演示argc的用法)
    #include 
    
    int main(int argc,char *argv[]){
        int i = 0;
        for(i = 0; i < argc; i++){
            printf("argv[%d]: %s\n",i,argv[i]); 
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    • PRO/demo18.c(演示execl执行另一程序的样子)
    #include 
    #include 
    #include 
    //函数原型:int execl(const char *path, const char *arg, ...);
    
    int main(void){
        printf("before execl\n");
        
        if(execl("./echoarg","echoarg","abc",NULL) == -1){
            printf("execl failed!\n");      
    	perror("why");
        }
        
        printf("after execl\n");//execl调用失败才会走到这里
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    • PRO/demo19.c(execl 执行 ls 指令)
    #include 
    #include 
    #include 
    //函数原型:int execl(const char *path, const char *arg, ...);
    
    int main(void){
        printf("before execl\n");
        
        if(execl("/bin/ls","ls",NULL,NULL) == -1){
            printf("execl failed!\n");      
    	perror("why");
        }
        
        printf("after execl\n");
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    • PRO/demo20.c(execl 执行 ls -l 指令)
    #include 
    #include 
    #include 
    //函数原型:int execl(const char *path, const char *arg, ...);
    
    int main(void){
        printf("before execl\n");
        
        if(execl("/bin/ls","ls","-l",NULL) == -1){
            printf("execl failed!\n");      
    	perror("why");
        }
        
        printf("after execl\n");
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    • PRO/demo21.c(execl 执行 /bin/date 下的 data 文件)
    #include 
    #include 
    #include 
    //函数原型:int execl(const char *path, const char *arg, ...);
    
    int main(void){
        printf("this pro get system date:\n");
    	
        if(execl("/bin/date","date",NULL,NULL) == -1){
            printf("execl failed!\n");      
    	perror("why");
        }
        
        printf("after execl\n");
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    • PRO/demo22.c(execlp函数带p,能通过环境变量PATH查找到可执行文件ps)(带p的只是省略了绝对路径,编译结果都一样)
    #include 
    #include 
    #include 
    //函数原型:int execl(const char *path, const char *arg, ...);
    
    int main(void){
        printf("this pro get system date:\n");
    	
        if(execlp("ps","ps",NULL,NULL) == -1){
            printf("execl failed!\n");      
    	perror("why");
        }
        
        printf("after execl\n");
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述
    echo $PATH:让系统能找到这个路径底下的可执行程序
    在这里插入图片描述

    • 将当前路径添加到系统环境变量(不用“./”、在别的目录下也可直接执行那个程序)
    pwd//return:/home/cjq/Jessie/PRO
    export PATH=$PATH:/home/cjq/Jessie/PRO
    
    • 1
    • 2

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    • PRO/demo23.c(编译结果同demo22)(带v的只是多了一个char *argv)
    #include 
    #include 
    #include 
    //函数原型:int execl(const char *path, const char *arg, ...);
    
    int main(void){
        printf("this pro get system date:\n");
    
        char *argv[] = {"ps",NULL,NULL};
        if(execvp("ps",argv) == -1){
            printf("execl failed!\n");      
    	perror("why");
        }
        
        printf("after execl\n");
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • PRO/demo24.c(编译结果同demo22、23)(带v的只是多了一个char *argv)(不带p要加上绝对路径)
    char *argv[] = {"ps",NULL,NULL};
        if(execv("/bin/ps",argv) == -1){
    
    • 1
    • 2

    11. exec族函数配合fork使用(11)

    在这里插入图片描述

    • 实现功能:当父进程检测到输入为1的时候,创建子进程把配置文件的字段值修改掉
    • PRO/demo25.c(仅使用子进程方式修改配置文件中的参数)
    #include 
    #include 
    #include 
    #include //wait的
    #include //wait的
    #include 
    #include 
    #include 
    
    int main(){
    	pid_t pid;
    	int data = 10;
    
    	while(1){
    		printf("please input a data\n");
    		scanf("%d",&data);	
    		if(data == 1){
    			int fdSrc;			
    		
    			pid = fork();
    			
    			if(pid > 0){
    				wait(NULL);
    			}	
    			if(pid == 0){
    				char *readBuf=NULL;
    
    				fdSrc = open("config.txt",O_RDWR);
    				int size = lseek(fdSrc,0,SEEK_END);
    				lseek(fdSrc,0,SEEK_SET);
    
    				readBuf=(char *)malloc(sizeof(char)*size + 8);
    				int n_read = read(fdSrc, readBuf, size);
    
    				char *p = strstr(readBuf,"LENG=");
    				if(p==NULL){
    					printf("not found\n");
    					exit(-1);
    				}
    				p = p+strlen("LENG=");
    				*p = '5';
    
    				lseek(fdSrc,0,SEEK_SET);
    				int n_write = write(fdSrc,readBuf,strlen(readBuf));
    				
    				close(fdSrc);
    				exit(0);
    			}
    		}
    		else{
    			printf("wait ,do nothing\n");
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • PRO/demo26.c(子进程用exec调用另一程序来修改配置文件中的参数)
    #include 
    #include 
    #include 
    #include //wait的
    #include //wait的
    #include 
    #include 
    #include 
    
    int main(){
    	pid_t pid;
    	int data = 10;
    
    	while(1){
    		printf("please input a data\n");
    		scanf("%d",&data);	
    		if(data == 1){
    			int fdSrc;			
    			pid = fork();
    		
    			if(pid > 0){
    				wait(NULL);
    			}	
    			if(pid == 0){
    				execl("./changeData","changeData","config.txt",NULL);
    			}
    		}
    		else{
    			printf("wait ,do nothing\n");
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    12. system函数(424.12)

    在这里插入图片描述

    • 精彩博文:linux system 函数详解
      • https://www.cnblogs.com/leijiangtao/p/4051387.html
    • system()函数的返回值如下:
      • 成功,则返回进程的状态值;
      • 当sh不能执行时,返回127;
      • 失败返回-1;
    • PRO/demo27.c
    #include 
    #include 
    #include 
    #include //wait的
    #include //wait的
    #include 
    #include 
    #include 
    
    int main(){
    	pid_t pid;
    	int data = 10;
    
    	while(1){
    		printf("please input a data\n");
    		scanf("%d",&data);	
    		if(data == 1){
    			int fdSrc;			
    		
    			pid = fork();
    			
    			if(pid > 0){
    				wait(NULL);
    			}	
    			if(pid == 0){
    				
    				//execl("./changeData","changeData","config.txt",NULL);
    				system("./changeData config.txt");//相当于在终端执行shell命令“sh -c ./changeData config.txt”
    			}
    		}
    		else{
    			printf("wait ,do nothing\n");
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    在这里插入图片描述

    13. popen函数(425.13)

    在这里插入图片描述

    • linux下popen的使用心得
      • https://blog.csdn.net/libinbin_1014/article/details/51490568
    • 比system在应用中的好处:
      • 可以获取运行的输出结果
    • PRO/demo28.c(system(exec)运行成功不返回)
    #include 
    #include 
    #include 
    //函数原型:int execl(const char *path, const char *arg, ...);
    
    int main(void){
        char ret[1024] = {0};
    
        system("ps");
        printf("ret=%s\n",ret);
    	//popen("ps","w");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • PRO/demo29.c(popen返回运行结果)
    #include 
    #include 
    #include 
    //函数原型:int execl(const char *path, const char *arg, ...);
    //FILE *popen(const char *command, const char *type);
    //size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
    //int pclose(FILE *stream);
    int main(void){
        char ret[1024] = {0};
        FILE *fp;
    
        fp = popen("ps","r");
        int nread = fread(ret,1,1024,fp);	
    
        printf("read ret %d byte, ret=%s\n",nread,ret);
    	    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    14. 进程总结(426.14)(重点:1 2 3 4 5 12 13节课程)

  • 相关阅读:
    OpenID Connect Federation 入门指南
    移动端 rem 适配方案
    Java项目:JSP健身房管理系统
    idea必装的插件 Spring Boot Helper 插件(创建 Spring Boot 项目)
    力扣题解(54. 螺旋矩阵),带注释
    reactnative保存图片到相册
    Docker Desktop 开启失败 Unexcept WSL Error
    题目 1061: 二级C语言-计负均正
    【gitlab】我的gitlab经历--20220901
    web开发概述
  • 原文地址:https://blog.csdn.net/Jaci133/article/details/133843993