进程控制

1 篇文章 0 订阅

一、进程的概念

    在UNIX 中,进程是正在执行的程序。它相当于Windows 环境内的任务这一概念。每个进程包括程序代码和数据。其中数据包含程序变量数据、外部数据和程序堆栈等。Linux 同样向程序员提供一些进程控制方面的系统调用,其中最重要的有以下几个:

         1.fork()。它通过复制调用进程来建立新的进程,它是最基本的进程建立操作。

         2.exec。它包括一系列的系统调用,其中每个系统调用都完成相同的功能,即通过用

一个新的程序覆盖原内存空间,来实现进程的转变。各种exec 系统调用之间的区别仅在于

它们的参数构造不同。

         3.wait()。它提供了初级的进程同步措施,它能使一个进程等待,直到另一个进程结

束为止。

         4.exit()。这个系统调用常用来终止一个进程的运行。

二、fork

    系统调用fork()是建立进程的最基本操作,它是把Linux 变换为多任务系统的基础。fork()在Linux 系统库unistd.h 中的函数声明如下:

              pid_t fork(void);

我们举个例子:

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

main()
{
	pid_t pid;
	int nCount = 0;
	pid = fork();
	if (!pid)
	{
		printf("I'm the child, my process id is %d, parent id is %d\n", getpid(), getppid());
		nCount++;
	}
	else if (pid > 0)
	{
		printf("I'm the parent, my process id is %d, parent id is %d, child has pid %d\n", getpid(), getppid(), pid);
		nCount++;
	}
	else
	{
		printf("Fork fail!\n");
	}

	printf("nCount = %d\n", nCount);  
}

运行结果:

guoqh@ubuntu:~/test$ ./a.out
I'm the parent, my process id is 2612, parent id is 2460, child has pid 2613
nCount = 1
guoqh@ubuntu:~/test$ I'm the child, my process id is 2613, parent id is 1
nCount = 1

在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……
        
为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
    1
)在父进程中,fork返回新创建子进程的进程ID;
    2
)在子进程中,fork返回0;
    3
)如果出现错误,fork返回一个负值;

         fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

         fork出错可能有两种原因:
    1
)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
    2
)系统内存不足,这时errno的值被设置为ENOMEM。
   
创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。


三.exec系列

    如果fork()是程序员唯一可使用的建立进程的手段,那么Linux 的性能会受很大影响。

因为fork()只能建立相同程序的副本,父子进程间不共享这些存储空间

    幸运的是,Linux 还提供了系统调用exec 系列,它可以用于新程序的运行。exec 系列中的系统调用都完成相同的功能,它们把一个新程序装入调用进程的内存空间,来改变调用进程的执行代码,从而形成新进程。如果exec 调用成功,调用进程将被覆盖,然后从新程序的入口开始执行。这样就产生了一个新的进程,但是它的进程标识符与调用进程相同。这就是说,exec没有建立一个与调用进程并发的新进程,而是用新进程取代了原来的进程。所以,对exec 调用成功后,没有任何数据返回,这与fork()不同。

下面给出了exec 系列调用在Linux 系统库中unistd.h 中的函数声明:

int execl( const char *path, const char *arg, ...);
int execlp( const char *file, const char *arg, ...);
int execle( const char *path, const char *arg , ..., char* const envp[]);
int execv( const char *path, char *const argv[]);
int execvp( const char *file, char *const argv[]);

exec函数族装入并运行程序path/file,并将参数arg0(arg1, arg2, argv[], envp[])传递给子程序,出错返回-1.

exec函数族中,后缀lvpe指定函数将具有某种操作能力:

后缀

操作能力

l

希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志

v

希望接收到一个以NULL结尾的字符串数组的指针

p

是一个以NULL结尾的字符串数组指针,函数可以DOSPATH变量查找子程序文件

e

函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境


系统调用exec 和fork()联合起来为程序员提供了强有力的功能。我们可以先用fork()建立子进程,然后在子进程中使用exec,这样就实现了父进程运行一个与其不同的子进程,并且父进程不会被覆盖。如下例1中exec 和fork()的联用了,例2中execl把父进程给覆盖了,导致后面的其他几个函数没有执行。

例1:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main(int argc, char *argv[])
{
  //以NULL结尾的字符串数组的指针,适合包含v的exec函数参数
  char *arg[] = {"ls", "-a", NULL};
  
  /**
   * 创建子进程并调用函数execl
   * execl 中希望接收以逗号分隔的参数列表,并以NULL指针为结束标志
   */
  if( fork() == 0 )
  {
    // in clild 
    printf( "1------------execl------------\n" );
    if( execl( "/bin/ls", "ls","-a", NULL ) == -1 )
    {
      perror( "execl error " );
      exit(1);
    }
  }
  
  /**
   *创建子进程并调用函数execv
   *execv中希望接收一个以NULL结尾的字符串数组的指针
   */
  if( fork() == 0 )
  {
    // in child 
    printf("2------------execv------------\n");
    if( execv( "/bin/ls",arg) < 0)
    {
      perror("execv error ");
      exit(1);
    }
  }
  
  /**
   *创建子进程并调用 execlp
   *execlp中
   *l希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
   *p是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件
   */
  if( fork() == 0 )
  {
    // in clhild 
    printf("3------------execlp------------\n");
    if( execlp( "ls", "ls", "-a", NULL ) < 0 )
    {
      perror( "execlp error " );
      exit(1);
    }
  }
  
  /**
   *创建子里程并调用execvp
   *v 望接收到一个以NULL结尾的字符串数组的指针
   *p 是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件
   */
  if( fork() == 0 )
  {
    printf("4------------execvp------------\n");
    if( execvp( "ls", arg ) < 0 )
    {
      perror( "execvp error " );
      exit( 1 );
    }
  }
  
  /**
   *创建子进程并调用execle
   *l 希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
   *e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境
   */
  if( fork() == 0 )
  {
    printf("5------------execle------------\n");
    if( execle("/bin/ls", "ls", "-a", NULL, NULL) == -1 )
    {
      perror("execle error ");
      exit(1);
    }
  }
  
  /**
   *创建子进程并调用execve
   * v 希望接收到一个以NULL结尾的字符串数组的指针
   * e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境
   */
  if( fork() == 0 )
  {
    printf("6------------execve-----------\n");
    if( execve( "/bin/ls", arg, NULL ) == 0)
    {
      perror("execve error ");
      exit(1);
    }
  }
  return EXIT_SUCCESS;
}
运行结果:

3------------execlp------------
4------------execvp------------
2------------execv------------
5------------execle------------
1------------execl------------
6------------execve-----------
.  ..  a.out  test.cpp
.  ..  a.out  test.cpp
.  ..  a.out  test.cpp
.  ..  a.out  test.cpp
.  ..  a.out  test.cpp
.  ..  a.out  test.cpp

例2:把例1中的创建新进程部分去掉

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main(int argc, char *argv[])
{
  //以NULL结尾的字符串数组的指针,适合包含v的exec函数参数
  char *arg[] = {"ls", "-a", NULL};
  
	printf( "1------------execl------------\n" );
	if( execl( "/bin/ls", "ls","-a", NULL ) == -1 )
	{
	  perror( "execl error " );
	  exit(1);
	}

	printf("2------------execv------------\n");
	if( execv( "/bin/ls",arg) < 0)
	{
	  perror("execv error ");
	  exit(1);
	}

	printf("3------------execlp------------\n");
	if( execlp( "ls", "ls", "-a", NULL ) < 0 )
	{
	  perror( "execlp error " );
	  exit(1);
	}

	printf("4------------execvp------------\n");
	if( execvp( "ls", arg ) < 0 )
	{
	  perror( "execvp error " );
	  exit( 1 );
	}

	printf("5------------execle------------\n");
	if( execle("/bin/ls", "ls", "-a", NULL, NULL) == -1 )
	{
	  perror("execle error ");
	  exit(1);
	}
	
	printf("6------------execve-----------\n");
	if( execve( "/bin/ls", arg, NULL ) == 0)
	{
	  perror("execve error ");
	  exit(1);
	}

	return EXIT_SUCCESS;
}
运行结果:
1------------execl------------
.  ..  a.out  test.cpp



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值