进程的基本概念及操作

一.进程的基本概念

    1.进程与程序的区别?

  • 程序是放到磁盘的可执行文件
  • 进程是指程序执行的实例 
  • 进程是程序的一次运行活动,属于动态的概念。程序是一组有序的静态指令,是一种静态的概念。但是,进程离开了程序也就没有了存在的意义。因此可以这样说:进程是执行程序的动态过程,而程序是进程运行的静态文本

    2.进程的执行状态

  • 执行状态

    进程正在占用CPU。

  •  就绪状态

    进程已具备一切条件,等待分配CPU。

  •  等待状态

    进程不能使用CPU,若等待的事件发生则可将其唤醒。

二.进程的创建

1.进程ID:每个进程都有一个ID(ID是一个正整数),唯一标识了系统中的这个进程。
每个进程都有一个创建它的进程,叫父进程(Parent Process)

进程ID(PID):标识进程的唯一数字
 
父进程ID(PPID)
 
启动进程的用户ID(UID)

2.进程的生命周期

 创建
    每个进程都由其父进程创建。父进程可以创建子进程,子进程又可以创建子进程的子进程。
 运行
    多个进程可以同时存在,进程之间可以进行通信。
 终止
    结束一个进程的运行。      

获取ID

#include <sys/types.h>
#include <unistd.h>
 
获取本进程ID
pid_t getpid(void)
 
获取父进程ID
pid_t getppid(void)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
 
int main()
{
     printf ("PID = %d\n", getpid());
     printf ("PPID = %d\n", getppid());
  
     return 0;
}
 

进程创建fork() 与 vfork()

fork()操作 

#include <unistd.h>
 
pid_t fork(void)
 
功能:创建子进程
fork的奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值:
1、在父进程中,fork返回新创建的子进程的PID;
2、在子进程中,fork返回0;
3、如果出现错误,fork返回一个负值
 
  •  当fork()顺利完成任务时,就会存在两个进程,每个进程都从fork()返回处开始继续执行。         
  •  两个进程执行相同的代码(text)段,但是有各自的堆栈(stack)段、数据(data)段以及堆(heap)。
  •  子进程的stack、data、heap segments是从父进程拷贝过来的。
  •  fork()之后,哪一个进程先执行(scheduled to use the CPU)不确定。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
	pid_t pid;
	
	pid = fork();

	if(pid < 0)
	{
		printf("fork error!\n");
		return -1;
	}
	else if(pid > 0)
	{
		printf("the parent id is %d\n",getpid());
		while(1);
	}
	else if(pid == 0)
	{	
		printf("the child id is %d\n",getpid());
		while(1);
	}

	return 0;
	
}

运行结果:在pid=fork()之前,只有一个进程在执行,但在这条语句执行之后,就变成两个进程在执行了,这两个进程的共享代码段,将要执行的下一条语句都是if(pid==0)。两个进程中,原来存在的那个进程被称作“父进程“,新出现的那个进程被称作“子进程”,父子进程的区别在于进程标识符(PID)不同。

the child id is 21389
the parent id is 21388

 连续调用三次fork()函数 当前程序产生了多少新的进程?

the child id is 21212
the parent id is 21211
the child id is 21213
the parent id is 21210
the child id is 21215
the parent id is 21214
the child id is 21216
the parent id is 21209

fork() vs vfork()  

区别:

1.fork:子进程拷贝父进程的数据段

2.vfork:子进程与父进程共享数据段

3.fork:父,子进程的执行次序不确定

4.vfork:子进程先运行,父进程后运行

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
 
int main()
{
    pid_t pid;
    int cnt = 0;
    pid = fork();

    if(pid<0)
    {    
        printf("error in fork!\n");
    }
    else if(pid == 0)
    {
        cnt++;
        printf("cnt = %d\n",cnt);
        printf("child pid = %d\n",getpid());
    }
    else
    {
        cnt++;
        printf("cnt = %d\n",cnt);
        printf("parent pid = %d\n",getpid());
    }
    return 0;
}

运行结果:

cnt=1
child pid = 21216
cnt=1
parent pid = 21209

为什么不是2 呢?因为fork()函数子进程拷贝父进程的数据段代码段,所以 cnt++; 将被父子进程各执行一次,但是子进程执行时使自己的数据段里面的(这个数据段是从父进 程那copy 过来的一模一样)count+1,同样父进程执行时使自己的数据段里面的count+1, 他们互不影响,与是便出现了如上的结果.

那么对于vfork()

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
 
int main()
{
    pid_t pid;
    int cnt = 0;
    pid = vfork();

    if(pid < 0)
    {
        printf("error in fork!\n");
    }
    else if(pid == 0)
    {
        cnt++;
        printf("cnt = %d\n",cnt);
        printf("child pid = %d\n",getpid());
       _exit(0);
    }
    else
    {
        cnt++;
        printf("cnt = %d\n",cnt);
        printf("parent pid = %d\n",getpid());
    }
    return 0;
 
}

 运行结果:

cnt = 1
child pid = 21218
cnt = 2
parent pid = 21229

如果没有_exit(0)的话,子进程没有调用exec 或exit,所以父进程是不可能执行的,在子 进程调用exec 或exit 之后父进程才可能被调度运行。 所以我们加上_exit(0);使得子进程退出,父进程执行,这样else 后的语句就会被父进程执行, 又因在子进程调用exec 或exit之前与父进程数据是共享的,所以子进程退出后把父进程的数 据段count改成1 了,子进程退出后,父进程又执行,最终就将count变成了2.

三.exec函数族

exec用被执行的程序替换调用它的程序
区别:
fork    创建一个新的进程,产生一个新的PID
exec   启动一个新程序,替换原有的进程,因此进程的PID不会改变。

1.execl()

#include <unistd.h>
 
int execl(const char * path, const char* arg1,...)
参数:
path : 被执行程序名(含完整路径)。
arg1 - argn: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
#include <stdio.h>
#include <unistd.h>
 
int main()
{

  //execl ("/bin/ls", "ls", "-al", "/home", NULL);

  int ret;
  ret = execl("/mnt/hgfs/Share/0414/server1","sever1","192.168.1.156",NULL);执行该路径下的程序
  if(ret < 0)
  {
    perror("execl");
    return -1;
  }
  
  return 0;
}
 

2.execlp()

#include <unistd.h>
 
int execlp(const char * path, const char* arg1,...)
参数:
path : 被执行程序名(不含路径,将从path环境变量中查找该程序)。
arg1 - argn: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。

3.execv()

#include <unistd.h>
 
int execv(const char * path, const char *argv[])
参数:
path : 被执行程序名(含完整路径)。
argv[]: 被执行程序所需的命令行参数数组。
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
 
int main()
{
	int ret;
 /*
	char *argv[] = {"ls","/home",NULL};
	ret = execv("/bin/ls",argv); 
	if(ret < 0)
	{
		perror("execv");
		return -1;
	}
 */
	char *argv[] = {"excv1",NULL};
	ret = execv("/mnt/hgfs/share/0414/excv1",argv1);  
	if(ret < 0)
	{
		perror("execv");
		return -1;
	}

    return 0;
 
}
 

4.systerm()

#include <stdlib.h>
 
int system(const char* string)
功能:
调用fork产生子进程,由子进程来调用 /bin/sh -c string来执行参数string所代表的命令

四.进程的终止 

1.exit()与_exit()

  • exit,_exit用于终止进程
  • _exit: 直接使进程停止,清除其使用的内存,并清除缓冲区中内容
  • exit与 _exit的区别:在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程。

 

#include<stdlib.h>
void exit(int status);
函数说明:
exit()用来正常终结目前进程的执行,并把参数status返回给父进程,而进程所有的缓冲区数据会自动写回并关闭未关闭的文件。
#include<unistd.h>
void _exit(int status);
函数说明
此函数调用后不会返回,并且会传递SIGCHLD信号给父进程,父进程可以由wait函数取得子进程结束状态。

2.僵尸进程 :子进程先比父进程退出

僵尸进程指的是那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸。
如何产生?
如果一个进程在其终止的时候,自己就回收所有分配给它的资源,系统就不会产生所谓的僵尸进程了
僵尸进程产生的过程:

  •  父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中(进程描述符占有极少的内存空间)。
  • 子进程的状态变成EXIT_ZOMBIE,并且向父进程发送SIGCHLD 信号,父进程此时应该调用 wait() 系统调用来获取子进程的退出状态以及其它的信息。在 wait 调用之后,僵尸进程就完全从内存中移除。
  • 因此一个僵尸存在于其终止到父进程调用 wait 等函数这个时间的间隙,一般很快就消失,但如果编程不合理,父进程从不调用 wait 等系统调用来收集僵尸进程,那么这些进程会一直存在内存中。

3.守护进程:父进程比子进程先退出

  • 若父进程比子进程先终止,则该父进程的所有子进程的父进程都改变为init进程。我们称这些进程由init进程领养。其执行顺序大致如下:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程ID就更改为1(init进程的ID);
  • 有init领养的进程不会称为僵死进程,因为只要init的子进程终止,init就会调用一个wait函数取得其终止状态。这样也就防止了在系统中有很多僵死进程。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
	pid_t pid;
	int i;
	pid = fork();

	if(pid < 0)
	{
		return -1;
	}
	else if(pid > 0)
	{
		exit(0);
	}

	setsid();//创建一个会话,将其变成会话组组长,脱离控制终端

	pid = fork();
	if(pid < 0)
	{
		return -1;
	}
	else if(pid > 0)
	{
		exit(0);
	}

	chdir("/");//改变路径
	umask(0);

	for(i = 0; i < getdtablesize(); i++)//关闭所有文件描述符
	{
		close(i);
	}

	while(1)
	{
		system("echo test >> /test.log");//每个5秒往里面写如test
		sleep(5);
	}

	return 0;
}

这里面涉及到一个函数getdtablesize() 

#include <unistd.h>

int getdtablesize(void);

简单来说:它是用来返回这个进程的文件描述表的项数,也就是说这个进程打开的文件的数目

五.进程等待

父进程创建子进程后,如何知道子进程什么时候终止?如何知道子进程怎么终止(正常or异常)?
措施:wait()或waitpid()      

#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options); 
返回值:若成功返回进程ID,若出错返回-1。

 调用wait或waitpid的进程可能发生的情况有:

  • 如果所有子进程都还在运行,则阻塞(Block)。
  • 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
  • 如果它没有任何子进程,则立即出错返回。
  • 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。
  • waitpid并不等待在其调用之后的第一个终止的子进程。它有若干个选项,可以控制它所等待的进程。
  • 如果一个子进程已经终止,并且是一个僵死进程,wait立即返回并取得该子进程的状态,否则wait使其调用者阻塞直到一个子进程终止。如果调用者阻塞并且它有多个子进程,则在其一个子进程终止时,wait就立即返回。因为wait返回终止子进程的ID,所以总能了解到是哪一个子进程终止了。

 注:僵死进程(zombie),一个已经终止、但是其父进程尚未对其进行善后处理(获得终止子进程的有关信息,释放它仍占用的资)的进程被称为僵死进程。

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

int main()
{
	pid_t pid;
	int status;
	pid = fork();

	if(pid < 0)
	{
		printf("fork error\n");
		return -1;
	}
	else if(pid > 0)
	{
		printf("parent pid is %d\n",getpid());
		wait(&status);
		printf(" WIFEXITED", WIFEXITED(status));
		printf("WIFSIGNALED", WIFSIGNALED(status));
	}
	else if(pid == 0)
	{
		printf("child pid is %d\n",getpid());
		while(1);
	}

	return 0;
}

waitpid() 

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid (pid_t pid, int * status, int options)
功能:会暂时停止目前进程的执行,直到有信号来到或子进程结束
 
参数:如果不在意结束状态值,则参数status可以设成NULL。
参数pid为欲等待的子进程识别码:
pid<-1 等待进程组识别码为pid绝对值的任何子进程。
pid=-1 等待任何子进程,相当于wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为pid的子进程。
 
参数option可以为0 或下面的OR 组合
WNOHANG:  如果没有任何已经结束的子进程则马上返回,不予以等待。
WUNTRACED :如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。

返回值:如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。

 



  

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值