进程的简单介绍与实现

什么是进程?什么是程序?
进程在系统中能够独立运行,并分配资源的基本单位。而程序由多个进程组成,一个程序最少有一个进程。进程是动态的,程序是静态的。

进程控制块(PCB)

在Linux中task_struct结构体即是PCB。PCB是进程的唯一标识,PCB由链表实现(为了动态插入和删除)。
进程创建时,为该进程生成一个PCB;进程终止时,回收PCB。
PCB包含信息:1、进程状态(state);2、进程标识信息(uid、gid);3、定时器(time);4、用户可见寄存器、控制状态寄存器、栈指针等(tss)
 
每个进程的task_struct和系统空间堆栈存放位置如下:两个连续的物理页【《Linux内核源代码情景分析》271页】

系统堆栈空间不能动态扩展,在设计内核、驱动程序时要避免函数嵌套太深,同时不宜使用太大太多的局部变量,因为局部变量都是存在堆栈中的。

每个进程都有一个 非负唯一进程ID(PID)。虽然是唯一的,但是PID可以重用,当一个进程终止后,其他进程就可以使用它的PID了。
PID为0的进程为调度进程,该进程是内核的一部分,也称为系统进程;PID为1的进程为init进程,它是一个普通的用户进程,但是以超级用户特权运行;PID为2的进程是页守护进程,负责支持虚拟存储系统的分页操作。

在linux系统可通过如下命令进行进程操作:
查看进程:
ps      查看当前进程
top      查看系统的所有进程
杀死进程:
kill  pid   ---》尝试杀死进程
kill -9  pid  ---》发送终止信号
其他操作:
./hello  &  ---》后台运行
killalll   进程的名称   ---》如果不知道进程的pid可以用此方法
killall  -STOP   进程名   ---》暂停进程
killall  -CONT  进程名   ---》继续运行进程

进程的运行状态示意图:


---------------------------------------------------------------------
1.进程的创建
一个进程的创建必须依赖另一个进程调用fork函数创建。
头文件:
         #include <unistd.h>
函数原型:
         pid_t  fork(void);
返回值:成功返回子进程的pid号,失败返回-1
注意:因为子进程会拷贝父进程fork后的内容作为自己的信息,所以fork函数就提供了两个返回值用于区别是父进程还是子进程。此外,父子进程在fork后,他们的内存空间是完全独立的。

fork()  == 0  ---》子进程里面的内容
fork() > 0 ---》父进程里面的内容
fork()  < 0  ---》创建进程失败

2.获取进程号
头文件:
         #include <sys/types.h>
         #include <unistd.h>
函数原型:
         pid_t  getpid(void);   ---》获取当前进程号
         pid_t  getppid(void); ---》获取父进程号

3.父进程等待子进程结束
头文件:
         #include  <sys/types.h>
         #include <sys/wait.h>
函数原型:
         pid_t  wait(int *status);
返回值:回收子进程pid号,失败返回-1
参数一:保存进程退出状态
函数原型:
         pid_t   waitpid(pid_t pid ,int *status,int options);
参数一:需要等待的子进程pid号
              pid = -1  等待任意进程,与wait()函数用法一样
              pid = 0  等待相同组的进程
参数二:保存进程的退出状态
参数三:设置该函数是否阻塞,0为阻塞,WNOHANG为不阻塞
-----------------------------------------------------------------------------
示例程序:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	pid_t pid = fork();
	int n=0;
	int status;
	if(pid == 0)
	{
		printf("创建子进程:pid = %d,它的父进程pid=%d\n",getpid(),getppid());
		exit(0);
	}
	else if(pid > 0)
	{
		wait(&status);//等待子进程退出,参数为NULL表示退出状态不关注,参数为&status为获取退出状态
		printf("子进程退出状态为%d\n",status);
		//waitpid(pid,NULL,0);//等待子进程退出,参数三0为阻塞,WNOHANG为不阻塞
		printf("父进程:pid = %d\n",getpid());
	}
	else
	{
		printf("创建进程失败!\n");
		exit(1);
	}
	return 0;
}


退出状态解析——宏函数
WIFEXITED() ---》判断是否正常退出,返回真与假
              WEXITSTATUS() --->获取正常退出的状态,成功返回状态值
WIFSIGNALED()   ---》判断是否被信号杀死,返回真与假
              WTERMSIG()   ---》获取杀死进程的信号值,成功返回信号值
WIFSTOPPED()   ---》判断是否给信号暂停,返回真与假
-------------------------------------------------------------------
练习:获取杀死子进程进程的信号值
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
	//创建子进程
	int pid =fork();
		if(pid==0)
		{	int i=0;
			while(1)
			{
				printf("i=%d\n",i++);
				sleep(1);
				if(i==100)
				{
					exit(5);
				}
			}
		}

	//等待子进程结束
	int status;
	int ret = wait(&status);
		/*
	    //判断是否为正常退出状态 
		if(WIFEXITED(status))
		{	//打印退出状态的值
			printf("ret=%d status=%d\n",ret,WEXITSTATUS(status));
		}
		else
		{
			printf("error exit\n");
		}
		*/
		
		//判断是否为信号杀死
		if(WIFSIGNALED(status))
		{	//获取杀死改进程的信号值
			printf("ret=%d status=%d\n",ret,WTERMSIG(status));
		}
		else
		{
			printf("error exit\n");
		}
		
	//exit(0);
	return 0;
}

---------------------------------------------------------------------
进程的退出标志:
return 0;  ---》函数的退出标志,但是假如在main函数中他就是进程的退出标志
头文件:
         #include <stdlib.h>
函数原型:
         void exit(int status);---》自己回收系统分配的资源,例如清空缓存区,但是该退出标志效率较低。
参数一:传递给父亲进程的一个退出标志
使用_exit或者_Exit退出进程的话,系统是不会回收资源的,必须要利用wait去回收,效率高
头文件:
         #include <unistd.h>
函数原型:
         void _exit(int status);
头文件:
         #include <stdlib.h>
函数原型:
         void _Exit(int status);

---------------------------------------------------------------------
使子进程与父进程脱离关系
exec函数族让子进程自己去跑一个main函数脱离父亲进程
头文件:
         #include <unistd.h>
函数原型:
         int execl(const char *path, const char *arg, ...);
         int execv(const char *path, const char * argv[]);
           int execle(const char *path, const char *arg, ..., char * const envp[]);
          int execlp(const char *file, const char *arg, ...);
         int execvp(const char *file, char *const argv[]);
         int execvpe(const char *file, char *const argv[], char *const envp[]);
以上所有exec函数族的返回值:失败发回-1 只要返回值不为-1都是执行成功

函数使用:
int execl(const char *path, const char *arg, ...);
参数一:要执行的子进程的绝对路径 
参数二:要执行的子进程名称与参数
注意传递参数的结束值必须为NULL;

 int execv(const char *path, const char * argv[]);
参数一:要执行的子进程的绝对路径 
参数二:要传递给子进程的参数
用法execv与execl是一样的,只不过一个传递的是数组,一个传递的是参数。

int execle(const char *path, const char *arg, ..., char * const envp[]);
用法execle与execl是一样的,只不过execle他可以传递环境变量值给子进程

例子1:   
execl("/home/gec/hello","hello",NULL);

例子2:传递参数
execl("/home/gec/hello","hello","nihao","wohao",NULL);  
---------------------------------------------------------------------------------------------------
练习:利用execl函数去调用其他进程作加法运算。     
int atoi(const char *nptr);把字符串转换为整型
int sprintf(buf,"%d",a);   把整形转换为字符串类型
-------------------------------add.c-----------------------------------------------
#include <stdio.h>
int main(int argv,char *argc[])
{
	printf("argc[1] = %s\n",argc[1]);
	printf("argc[2] = %s\n",argc[2]);

	int a = atoi(argc[1]);
	int b = atoi(argc[2]);
	int c = a+b;
	printf("result = %d\n",c);
	return 0;
}

---------------------------------execl.c--------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main(int argv,char *argc[])
{
	pid_t pid = fork();

	if(pid == 0)
	{
		//调用execl函数让子进程去执行其他函数
		// execl("/mnt/hgfs/ARM开发板/内核/add","add",argc[1],argc[2],NULL);
		char *arg[] = {"add",argc[1],argc[2],NULL};
		execv("/mnt/hgfs/ARM开发板/内核/add",arg);//参数一为要执行的子进程的绝对路径 ,参数二为要传递给子进程的参数
	}
	int status;
	int ret = wait(&status);
	if(WIFEXITED(status))//判断是否为正常退出状态
		printf("ret = %d,status = %d\n",ret,WEXITSTATUS(status));
	else
		printf("退出失败!\n");
	return 0;
}


带p的exec族函数:他是获取了系统的环境变量,可以不用写绝对路径
 *** 但是参数一的进程必须要在环境变量PATH中****
       int execlp(const char *file, const char *arg, ...);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[], char *const envp[]);
--------------------------------------------------------------------------------------------------
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
char command[100];
int main()
{
	int ret;
	while(1)
	{
		printf("输入要执行的命令>:");
		fgets(command,100,stdin);//将以回车结束输入的行缓冲区的内容给command
		command[strlen(command)-1] = '\0';//给数组添加结束符
		pid_t pid = fork();
		if(pid == 0)
		{
			execlp(command,command,NULL);//从PATH环境变量中查找输入的文件并执行,最后一个参数必须是NULL
			exit(errno);
		}
		else if(pid > 0)
		{
			wait(&ret);//等待子进程退出
			printf("子进程已退出,ret = %d\n",ret);
		}
		else
		{
			printf("创建进程失败!\n");
		}
	}
	return 0;	
}

init进程成为所有僵尸进程(孤儿进程)的父进程

僵尸进程

在进程调用了exit之后,该进程并非马上就消失掉,而是留下了一个成为僵尸进程的数据结构,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。

子进程结束之后为什么会进入僵尸状态? 因为父进程可能会取得子进程的退出状态信息。

如何查看僵尸进程?

linux中命令ps,标记为Z的进程就是僵尸进程。

#include <sys/types.h>
 2 #include <unistd.h>
 3 int main()
 4 {
 5  pid_t pid;
 6  pid = fork();
 7  if(pid < 0){
 8  printf("error occurred\n");
 9  }else if(pid == 0){
10  exit(0);
11  }else{
12  sleep(60);
13  wait(NULL);
14  }
15 }

守护进程Daemon

是linux的后台服务进程。它是一个生存周期较长的进程,没有控制终端,输出无处显示。用户层守护进程的父进程是init进程。
守护进程创建步骤:
1、创建子进程,父进程退出,子进程被init自动收养;fork    exit
2、调用setsid创建新会话,成为新会话的首进程,成为新进程组的组长进程,摆脱父进程继承过来的会话、进程组等;setsid
3、改变当前目录为根目录,保证工作的文件目录不被删除;chdir(“/”)
4、重设文件权限掩码,给子进程更大的权限;umask(0)
5、关闭不用的文件描述符,因为会消耗资源;close
 
一个守护进程的实例:每隔10s写入一个“tick”
复制代码
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<fcntl.h>
 4 #include<unistd.h>
 5 #include<sys/types.h>
 6 #define MAXFILE 65535
 7 8 int main()
 9 {
10  int fd,len,i;
11  pid_t pid;
12  char *buf = "tick\n";
13  len = strlen(buf);
14  if((pid = fork()) < 0){
15  perror("fork failed");
16  exit(1);
17  }
18  else if(pid > 0){
19  exit(0);
20  }
21  setsid();
22  if(chdir("/") < 0){
23  perror("chdir failed");
24  exit(1);
25  }
26  umask(0);
27  for(i = 0; i < MAXFILE; i++){
28  close(i);
29  }
30  while(1){
31  if((fd = open("/tmp/dameon.log", O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0){
32  perror("open log failed");
33  exit(1);
34  }
35  write(fd, buf, len+1);
36  close(fd);
37  sleep(10);
38  }
39 }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值