系统编程一:进程的概念+进程API

一、进程的概念

1、什么是程序? 什么是进程?

        程序:一堆待执行的代码。gcc hello.c -o  hello  静态的文本数据

        进程:当程序被CPU加载时,根据每一行代码做出相应的效果,形成动态的过程,那么这个过程就是进程。

        其实说白了,进程就是一个正在执行的程序。

2、在linux下,如何开启一个新的进程?

        直接在linux下,运行一个程序,就会开启相应的进程。

        比如  ./hello  --->开启名为 hello的进程。 

3、 当进程开启之后,系统会为进程分配什么资源?

        (1)会分配进程相应的内存空间

                int x --->申请空间

        (2)进程在系统中如何进行表示的?

                学生管理系统 ---》每一名同学的信息是通过结构体去存储和管理         
                航班管理系统  --》每列航班信息 通过 结构体 去存储 和 管理 
                linux操作系统(linux内核)--》也是通过 结构体 去进行存储和管理

           也就是说,当进程开启之后,系统会为这个进程分配一个任务结构体,这个任务结构体就是用来 描述这个进程。 struct task_struct{} --->进程控制块PCB。
            结构体 : 进程ID号 、信号、文件、资源....     

4、关于进程的命令

        (1)查看进程的ID号

        gec@ubuntu:~$ ps -ef  --查看当前的进程

        完整的指令是 ps -aux 

        可以看到它会展示很多进程。

        但是,在实际工作中,我们会配合 grep 来查找程序中是否存在某一个进程(grep 起到一个类似管道过滤的作用):        

        (2) 查看整个linux系统进程之间关系(pstree)

      

        (3) top  ---以CPU占有率 进行排行 进程

        使用 top 指令查看,类似Windows任务管理器,是动态的,会把相关的CPU,内存等信息显示出来,可以通过这个来评估看一个程序CPU占用情况以及内存消耗情况。

二、进程的状态

1、进程从诞生到死亡 会经历哪些状态 ?

        就绪态: 等待CPU资源  不占用CPU资源 ,不运行代码
        运行态: 占用CPU资源,运行代码。
        暂停态: 占用CPU资源, 不运行代码
        睡眠态: 占用CPU资源,不运行代码,可以回到就绪态。
        僵尸态: 占用CPU资源,不运行代码 ,进程退出的时候一定会变成僵尸态。需要父进程回收
        死亡态: 进程退出的时候,如果有人绑自己回收资源,那么僵尸态就会变成死亡态。释放资源

2、什么是僵尸态?

        进程结束的时候,就会从运行变成 僵尸态,所谓的僵尸态,就是代表这个进程所占用的CPU资源和自己本身。
        任务结构体没有被释放,这个状态的进程就是僵尸态进程。

三、进程的API接口

        单进程程序 ---》只能一行行代码去执行
        多进程程序---》同时执行两行代码 ---》产生一个子进程,帮自己去处理其他事情。

1、如何在一个正在执行的进程中产生一个子进程?(fork)

头文件
    #include <unistd.h>
函数原型
    pid_t fork(void);
函数作用 
    创建一个子进程,将父进程的资源复制一份,申请一片新的资源给子进程

返回值  
    成功:(一次调用,两次返回)
            >0    父进程  ,返回的ID号表示的是子进程的ID号
            ==0   子进程 
    失败 -1        

(1)例子1:

//fork01.c
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>


int main()
{
	printf("main\n");
	
	//产生一个子进程
	fork();
	
	printf("after fork\n");
}

        结果:

        还有一种运行结果是父进程先退出来了,所以打印了命令提示符:

         注意: 父进程退出之后,命令提示符就会显示出来
        结论: 此时 父进程 比 子 进程 先 执行。实际上 父进程 与子进程 执行的先后次序是随机的。

(2)例子2:

        父进程创建子进程之后,去处理其他事情:

//fork02.c
#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>


int main()
{
	printf("main\n");
	
	//产生一个子进程
	pid_t id = fork();
	
	/*一个是父进程  一个子进程*/
	
	if(id == -1)
	{
		printf("fork error\n");
		return -1;
	}
	else if(id > 0 )//父进程
	{
		sleep(1);//延时 1s,让子进程先执行
		printf("我是你爹,我的儿子id号是:%d\n",id);
	}
	else if(id == 0)//子进程
	{
		printf("好大儿\n");
	}
	
	
	printf("after fork:%d\n",id);
}

       总结:

        1) 父子进程执行顺序是 随机的。
        2)fork之后的代码,两个进程都会执行。

2、练习

        (1)练习1:

        写一个程序,使得子进程 每隔1s 就打印一次 hello  ,父进程每隔2s 就打印一次world。

/*练习1: 写一个程序,使得子进程 每隔1s 就打印一次 hello  ,父进程每隔2s 就打印一次world
*/

#include<stdio.h>
#include <unistd.h>

int main()
{

	//创建子进程
	pid_t id = fork();
	if(id == -1)
	{
		printf("fork error\n");
		return -1;
	}
	else if(id >0)//父进程 
	{
		while(1)
		{
			sleep(2);
			printf("parent world\n");			
		}

	}
	else if(id == 0)//子进程
	{
		while(1)
		{
			sleep(1);
			printf("\nchild hello\n");			
		}
	}

	
	return 0;
}

        (2)练习2:

        在一个进程中 打印 自己的进程ID号   和  父进程的ID号。

        #include <sys/types.h>
        #include <unistd.h>

        pid_t getpid(void); ---》返回 当前 进程的ID号
        pid_t getppid(void);---》返回 当前 进程的父进程ID号

#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>


int main()
{
	//printf("main\n");
	printf("main %d\n",getpid());
	//产生一个子进程
	pid_t id = fork();
	
	/*一个是父进程  一个子进程*/
	if(id == -1)
	{
		printf("fork error\n");
		return -1;
	}
	else if(id > 0 )//父进程
	{
		sleep(1);//延时 1s,让子进程先执行
		printf("父进程\n");
		printf("%d %d\n",getpid(),getppid());
	}
	else if(id == 0)//子进程
	{
		printf("子进程\n");
		printf("child:%d parent:%d\n",getpid(),getppid());
	}
	
	
	printf("after fork:%d\n",id);
}

四、孤儿进程 与 僵尸进程

        孤儿进程 :一般情况下,调用fork()函数创建的子进程,父进程如果比子进程先退出,那么这个子进程称之为 孤儿进程。那么,祖先进程就会称为该进程 的 父进程 ,回收该子进程的资源。

        僵尸进程:父进程还存在,但是去做别的事情了(比如在一个死循环里面,没有退出),此时子进程退出之后,就变成了僵尸进程。

1、僵尸进程的测试程序

#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>


int main()
{
	//printf("main\n");
	printf("main %d\n",getpid());
	//产生一个子进程
	pid_t id = fork();
	
	/*一个是父进程  一个子进程*/
	if(id == -1)
	{
		printf("fork error\n");
		return -1;
	}
	else if(id > 0 )//父进程
	{
		while(1)
		{
			sleep(1);
			printf("parent:%d 我正在忙其他事情\n",getpid());
		}
	}
	else if(id == 0)//子进程
	{
		printf("child:%d 我是儿子,而且是好大儿\n",getpid());
	}
	

}

        结果:

        通过ps -ef查看  ,当前 子进程 就变成了 僵尸进程 ,一直占用 系统资源,没有被回收 
gec       3463  3462  0 00:24 pts/1    00:00:00 [a.out] <defunct>

2、孤儿进程的测试程序

#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>


/*
孤儿进程 :一般情况下,调用fork()函数创建的子进程,父进程如果比子进程先退出,那么这个子进程称之为 孤儿进程。那么,祖先进程就会成为该进程 的 父进程 ,回收该子进程的资源。

*/
int main()
{
	//printf("main\n");
	printf("main %d\n",getpid());
	//产生一个子进程
	pid_t id = fork();
	
	/*一个是父进程  一个子进程*/
	if(id == -1)
	{
		printf("fork error\n");
		return -1;
	}
	else if(id > 0 )//父进程
	{	
		printf("parent:%d 我挂了\n",getpid());
	}
	else if(id == 0)//子进程
	{
		sleep(1);
		//延时1s之后,此时父进程已经退出了,子进程就变成了孤儿进程
		//孤儿进程 在失去父亲之后会马上 寻找继父
		printf("child:%d 我是儿子,而且是好大儿 等等我 %d\n",getpid(),getppid());
	}
	

}

        结果:

gec@ubuntu:/mnt/hgfs/gz2166/07-系统编程/01-进程的概念+进程API/1-code$ ./a.out 
main 3480
parent:3480 我挂了
gec@ubuntu:/mnt/hgfs/gz2166/07-系统编程/01-进程的概念+进程API/1-code$ child:3481 我是儿子,而且是好大儿 等等我 1 (子进程的父进程)
 

3、练习

        创建一个子进程 ,子进程打印hello后父进程打印world,之后 父进程 再创建另一个子进程 ,打印 end 。

        

#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>


/*
练习2:创建一个子进程 ,子进程打印hello后父进程打印world,之后 父进程 再创建另一个子进程 ,打印 end 


*/
int main()
{
	//产生一个子进程
	pid_t id1 = fork();
	if(id1 == -1)
	{
		printf("fork error\n");
		return -1;
	}
	else if(id1 > 0 )//父进程
	{	
		sleep(1);
		printf("parent:%d world\n",getpid());
		
		//父进程 再创建另一个子进程 ,打印 end 
		pid_t id2 = fork();
		if(id2 == -1)
		{
			printf("fork error\n");
			return -1;
		}
		else if(id2 > 0 )//父进程
		{
			sleep(1);
			printf("parent end\n");
		}
		else if(id2 ==0 )//子进程2
		{
			printf("child2:%d end\n",getpid());
		}
	}
	else if(id1 == 0)//子进程1
	{
		printf("child1:%d hello\n",getpid());
	}
	

}

五、等待子进程退出 ,主动 回收 资源  (wait)

    #include <sys/types.h>
    #include <sys/wait.h>

    pid_t wait(int *status);

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

函数作用: 等待一个进程退出,回收资源,也就是说 帮 这个子进程修改状态 (从僵尸态 变成  死亡态)

参数 :
        status  子进程的退出状态 ,如果 父 进程 不关心 子进程 是怎么挂的,那么这个值可以设置成 NULL
返回值:
        成功  返回  回收资源的那个子进程的ID号 
        失败 -1
 

注意: 
1、wait函数 属于一个阻塞函数,如果子进程没有退出 变成 僵尸态 ,那么这个函数就会阻塞,直到子进程变成僵尸态之后,才会将子进程的资源回收
    
2、退出状态 不等于 退出值 ,  退出状态 包括了 退出值,退出状态里面还有其他的 

#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/wait.h>


int main()
{
	//产生一个子进程
	pid_t id = fork();
	if(id == -1)
	{
		printf("fork error\n");
		return -1;
	}
	else if(id > 0 )//父进程
	{	
		printf("我是父亲\n");
	
		//父进程 阻塞 等待  子进程退出,回收子进程的资源
		int status;
		wait(&status);
		
	}
	else if(id == 0)//子进程
	{
		sleep(4);
		printf("child:%d hello\n",getpid());
		
	}
	
	
}

六、结束 当前进程(exit、_exit和_Exit)

#include <unistd.h>
#include <stdlib.h>
    
void exit(int status);
void _exit(int status);
void _Exit(int status);

函数作用: 让当前进程退出,并且可以设置自己的退出状态,传递给父进程。        
参数:
        status---》设置退出的状态值,可以让父进程获取到这个值 

1、exit 

exit - cause normal process termination

函数作用 : 先清洗缓冲区,再退出当前进程
         
2、 _exit和 _Exit

函数作用 :直接退出当前进程,不清洗缓冲区

1、例子1

        通过调用exit 和 _exit退出 当前进程

        exit:先清洗缓冲区,再退出 当前进程    
        _exit:直接退出当前进程

#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
	/*单进程的程序*/
	printf("main ");
		
	//先清洗缓冲区,再退出当前进程
	//exit(0);//return  0;
	
	//直接退出当前进程,不会帮助你清洗缓冲区
	_exit(0);
	
	printf("11112222\n");
}

  2、例子2

        获取子进程的退出值:

        

#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
	//创建一个子进程
	pid_t id = fork();
	if(id == -1)
	{
		
	}
	else if(id >0)//父进程
	{
		printf("parent:%d\n",getpid());
	

	}
	else if(id ==0)//子进程
	{
		printf("child:%d\n",getpid());
		exit(0);
		//调用了exit,子进程后面的代码都不会执行了
		printf("11111111111\n");
	}
	//下面的代码 子进程不会执行了,因为在子进程中已经调用了exit退出函数
	//等待子进程退出,并且获取子进程的状态值
	int status;
	wait(&status);
	//注意:status是 退出状态,里面包括了退出值
	//调用系统已经封装好的宏函数实现  从  退出状态中  提取 出 退出值 
	if(WIFEXITED(status))//先判断子进程是否正常退出
	{
		//使用 WEXITSTATUS 宏函数 获取 子进程的退出值
		printf("status:%d\n", WEXITSTATUS(status));
	}
}

3、例子3

        exit 跟 return 区别:

#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

//例子3:exit 跟 return 区别 

int func()
{
	exit(0);//退出整个 进程
	//return  0; //退出 当前 函数 
}

int main()
{
	/*单进程的程序*/
	printf("main\n ");
		
	//先清洗缓冲区,再退出当前进程
	//exit(0);
	func();
	
	printf("end\n");
}

结论: 

1、在main函数里面,return 和 exit 的作用都是一样的。
2、如果 是在某个函数的内部 ,return 表示退出当前函数 ,exit表示的是退出进程。

七、相关作业练习

1、作业1

         用进程相关API函数编程一个程序,使之产生一个进程扇:父进程产生一系列子进程,每个子进程打印自己的PID然后退出。要求父进程最后打印PID

 

#include<stdio.h>
#include<time.h>
#include<stdbool.h>
#include <unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>


int  main()
{
			
	/*优化*/
	int i = 0;
	int ret = 0;
	pid_t id;
	
	for(i = 0; i<4;i++)
	{
		if((id = fork())== 0)
		{
			break;
		}
	}

	if(id == 0)//子进程
	{
		printf("child%d:%d\n",i,getpid());
		exit(1);
	}	
	
	if(id > 0)//父进程
	{
		sleep(4);
		wait(NULL);
	}
	
		printf("parent:%d\n",getpid());
	
}

2、作业2

        用进程相关API函数编写一个程序,使之产生一个进程链:进程派生一个子进程后,父进程然后打印出自己的PID,然后退出,该子进程继续派生子进程,然后打印PID,然后退出,以此类推。

        要求:

        (1)实现一个父进程要比子进程先打印PID的版本。(即打印的PID一般是递增的)

        (2)实现一个子进程要比父进程先打印PID的版本。(即打印的PID一般是递减的)

         

 

#include<stdio.h>
#include<time.h>
#include<stdbool.h>
#include <unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>


int  main()
{
	int i = 0;
	int ret = 0;
	pid_t pid;
	
	for(i=0;i<4;i++)
	{
		pid = fork();
		if(pid > 0)//父进程 
		{
			wait(NULL);
			printf("parent:%d\n",getpid());
			break;
		}
		else if(pid == 0)//子进程
		{
			
			printf("child:%d\n",getpid());
		}
	}


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值