Liunx C 语言 - 进程

Liunx C 语言 - 进程

一 . 进程相关概念

1. 什么是程序?什么是进程?俩者的区别?

(1)程序是静态的概念,程序是指令的集合;进程是动态的概念,是程序在处理机上的一次执行的过程。
(2)进程是暂时的,是有生命周期的。

2. 如何查看进程?

a. ps 指令

ps -aux | grep 过滤条件 (ps -aux|grep init)

b. top 指令

类似于 windows 的 任务管理器

top
在这里插入图片描述

3. 什么是进程标识符?

进程标识符就是一个非负唯一的整数 。
在这里插入图片描述
c 语言中可以使用 getpid() 函数获取进程标识符。

4. 什么是父子进程?

父子进程是指一个进程可以创建其他进程,被创建的进程就成为原始进程的子进程。 在这种模型中,父进程和子进程之间存在一种层次结构,父进程是子进程的直接控制者和管理者。子进程在创建时自动复制了父进程的内存空间、数据等资源,在创建过程中修改的子进程不会对父进程有任何影响。

5. C程序的存储空间如何分配 ?

  1. 代码段:存储程序的指令,通常是只读内存区域。在程序启动时被加载到内存,并且在整个程序运行期间不会改变。
  2. 数据段:存储程序静态分配的全局变量和静态变量,它们在程序整个运行期间都存在,以及一些字符串常量等。它也是只读内存区域。
  3. 堆区:由程序员手动管理的动态内存分配空间,通过 malloc() 和 free() 等函数进行内存的申请和释放。因此它是可变的,当需要时可以动态地向系统申请更多的内存,反之亦然。
  4. 栈区:用于存储函数调用过程中的临时变量、参数以及返回地址等,具有“先进后出”的特点。每次函数调用时,系统会自动为其分配一块栈空间,在函数返回时释放。也就是说,它是由系统自动管理的,不能手动修改大小。

二. 进程的创建

1. 相关函数

头文件:
	#include <unistd.h>
函数:
	pid_t fork(void);
描述:
	通过复制调用进程来创建一个新进程。 新进程(称为子进程)是调用的精确副本,
	子进程拷贝父进程的数据段,代码段.父子进程的执行次序不确定.
----------------------------------------
头文件:
 	#include <sys/types.h>
    #include <unistd.h>
函数:
	 pid_t vfork(void);
描述:
	创建一个进程,vfork() 子进程与父进程共享数据段, vfork 保证子进程先运行,
	在调用exec 或exit 之前与父进程数据是共享的,在它调用exec或exit 之后父进程才可能被调度运行。

2. 使用案例

2-1. 模拟消息接入(fork)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(){
	pid_t retid ; 
	int number = 10 ; 
	int data = 0 ; 
	while(1){
		printf("plase scaner 1 to they \r\n");
		scanf("%d",&data);
		if(data == 1 ){
			data = 0 ; 
			retid = fork();
			if(retid > 0){
				// 用于证明数据独立
				number += getpid(); 
				while(1){
					// 证明创建了不同的子进程
					printf("this child pid is %d ,data is %d \r\n",getpid(),number);
					sleep(3);
				}
			}else if(retid == 0 ){
				printf("this is faher\r\n");	
			}
		}else{
			printf("do nothing \r\n");
			sleep(3);
		}
	}
	return 0 ; 
}
2-2. vfork
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main(){
	pid_t pid ; 
	pid =  vfork();
	// 子进程执行完后,才会执行父进程
	if(pid == 0 ){
		int i = 0 ;
		for(;i < 5 ; i++){
			printf("this is child -- %d \r\n",i);
			sleep(1);
		}
		exit(0);
	}else if(pid > 0 ){
		int i = 0 ;
		for(;i<20;i++){	
			printf("this is father \r\n");
			sleep(1);
		}
	}
	return 0 ; 
}

3. 使用场景

2-1. 服务器对于多任务处理
2-2. 比如脚本编写

三. 进程的退出

1.相关函数

头文件:
 	#include <stdlib.h>
函数:
	void exit(int status);
描述:
	exit() 函数导致正常进程终止,并将status&0377的值返回给父进程(参见wait(2))。
-------------------------------------------------------------
头文件:
	 #include <sys/types.h>
     #include <sys/wait.h>
函数:
 	pid_t wait(int *status);

    pid_t waitpid(pid_t pid, int *status, int options);

    int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
描述:
	父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个
	子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会
	收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进
	程,wait就会一直阻塞在这里,直到有一个出现为止。

2. 相关概念

a.正常退出
  1. main函数调用return

  2. 进程调用exit(),标准C库

  3. 进程调用_exit()或者_Exit(),系统调用

  4. 进程最后一个线程返回

  5. 最后一个线程调用pthread_exit

b.异常退出
  1. 调用abort

  2. 当进程收到某些信号时,如ctrl+c

  3. 最后一个线程对取消(cancellation)请求作出响应不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。

c. 父进程收集子进程退出状态

父进程等待子进程退出并收集子进程退出状态
子进程退出状态不被收集,会变成僵尸进程

3.使用案例

a. 父进程等待子进程退出(收集状态)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(){
	pid_t pid; 
	int status = 10 ;
	pid =  fork();
	if(pid  == 0 ){ // child process 
		int i = 0 ;
		for( ; i< 5 ; i++){
			printf("this child my pid is %d , myfather pid is %d \r\n",getpid(),pid);
			sleep(1);
		}
	}else if(pid >  0 ){ // father process	
		printf("等待子进程退出\r\n");
		wait(&status);
		printf("子进程退出,状态码为 :%d\r\n",WEXITSTATUS(status));
		while(1){
			printf("this is father process my pid is %d \r\n",getpid());
			sleep(2);
		}
	}
	return 0 ; 
}
b.僵尸进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(){
	pid_t pid; 
	pid =  fork();
	if(pid  == 0 ){ // child process 
		int i = 0 ; 
		for(;i < 5;i++){
			printf("this child my pid is %d , myfather pid is %d \r\n",getpid(),pid);
			sleep(3);
		}
		exit(-1);
	}else if(pid >  0 ){ // father process	
		while(1){
			printf("this is father process my pid is %d \r\n",getpid());
			sleep(2);
		}
	}
	return 0 ; 
}

在这里插入图片描述

c. 父进程优先于子进程退出(孤儿进程)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(){
	pid_t pid; 
	pid =  fork();
	if(pid  == 0 ){ // child process 
		int i = 0 ; 
		for(;i < 5;i++){
			printf("this child my pid is %d , myfather pid is %d \r\n",getpid(),getppid());
			sleep(3);
		}
		exit(-1);
	}else if(pid >  0 ){ // father process	
		while(1){
			printf("this is father process my pid is %d \r\n",getpid());
			sleep(2);
		}
	}
	return 0 ; 
}

在这里插入图片描述

四. 其他程序的调用 (exec族函数,system,popen)

详细介绍可以查看 : https://blog.csdn.net/u014530704/article/details/73848573

exec 函数

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
 
int main(){
 
        //int execl(const char *path, const char *arg, .../* (char  *) NULL */);
/*      path:可执行文件的路径名字
        arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
        file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件
        
        exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
        
        l : 使用参数列表
        p:使用文件名,并从PATH环境进行寻找可执行文件
        v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
        e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
        exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。 
*/
 
        printf("before execl\n");
        if(execl("/bin/ls","ls",NULL,NULL) == -1)//通过whereis指令找到ls命令的位置
        {
                printf("execl failed!\n");
                perror("why");
        }
                printf("after execl\n");
 
        return 0;
}
int main(){
 
        //int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
        printf("before execl\n");
        if(execlp("ls","ls","-l",NULL) == -1)//不用找到命令的路径
        {
                printf("execl failed!\n");
                perror("why");
        }
                printf("after execl\n");
 
        return 0;
}

可以通过 whereis ls 查找指令位置 , echo $PATH 查看环境变量,echo PATH=$PATH:/bin/data 添加data 指令

system (是对 exec 函数的封装,可执行后面的操作)

本质上是对execl函数的二次封装,可查看其源码。实际上比execl更好用
和execl区别:system执行完该函数后,还会继续执行后面的函数 ,而exec则不会

/*
	#include <stdlib.h>
	int system(const char *command);
	system()函数返回值如下:
	成功,则返回进程的状态值
	当sh不执行时,返回127;//shell脚本
	失败返回 -1
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
	 printf("before execl\n");
	 if(system("ls -l") == -1){
			 printf("execl failed!\n");      
	 }
	 printf("after execl\n");
	return 0;
}

popen (可获得返回结果)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
	 printf("before execl\n");
	 FILE *fd ;  
	 fd = popen("ls -l");
	 char readBuf[1024]	= {0};
	 fread(readBuf,1024,1,fd);
	 printf("%s\r\n",readBuf);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老痞啥都不会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值