天天看点

Linux编程之进程控制(1)

一.进程标识符

      UNIX一个被称为进程ID(process ID)的非负整型数表示进程,该进程ID为唯一的。虽然是唯一的,但是进程ID可以重用,即当一个进程终止后,其进程ID可以再被使用。大多数UNIX系统实现延迟重用算法,给予新建进程的进程ID不同于最近终止进程所使用的进程ID,这防止了将新进程误认为是使用同一ID的某个已终止的先前进程。

      系统中有一些专用进程:ID=0的进程是调度进程,是内核的一部分,并不执行任何磁盘上的程序,为系统进程;ID=1的进程是init进程,在自举过程结束时由内核调用,是一个普通的用户进程,负责启动一个UNIX系统,并读与系统有关的初始化文件,并将系统引导到一个用户状态,且决不会终止。

      进程ID(A)之上有个父进程ID(B),这个父进程ID(A)最初是创建进程ID(A)的那个进程的进程ID。当父进程终止时,其子进程被上一级父进程领养,当无上一级父进程时,都会被init进程领养。

      进程ID用pid_t表示,得到进程ID和父进程ID的函数如下:

#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
           

     系统会为用户分配整型用户ID(user ID)和整型用户组ID来指明此用户的使用权限,分别表示为uid_t、gid_t。其中,用户ID分为真实用户ID、有效用户ID,用户组ID分为真实用户组ID、有效用户组ID。通常,真实用户ID = 有效用户ID,真实用户组ID=有效用户组ID。但进程用有效ID来确定文件的访问权限。比如说,root权限运行的程序可能希望代表普通用户创建一个文件,通常将进程的有效用户ID设置为这个普通用户的ID,此时真实用户ID为root,有效用户ID为普通用户ID。其相关函数如下:

#include <unistd.h>
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);
           

二.创建进程

     创建进程有两种方式:fork、vfork,创建进程的进程为父进程,被创建的进程为子进程。创建后,两个进程都继续执行后面的代码。

     1. fork()

#include <unistd.h>
pid_t fork(void);  // 返回值:子进程返回0,父进程中返回子进程ID,出错返回-1,并设置errno
           

        fork函数被调用一次,返回两次,其中子进程的返回值为0,父进程的返回值为子进程ID。子进程是父进程的副本,子进程获得父进程数据空间、堆和栈的副本,父子进程并不共享存储空间,但共享正文段。

        父进程的很多属性都被子进程继承,如打开的文件的文件描述符等,但也有些属性不被继承,整理如下:

        1)fork的返回值不同

        2)各自的进程ID和父进程ID不同

        3)子进程的CPU的使用时间重置为0,tms_utime、tms_stime、tms_cutime、tms_ustime都被重置为0

        4)父进程的锁不会被子进程继承

        5)子进程中不会继承父进程的alarm

        6)子进程的未处理信号集为空集

     2. vfork()

         vfork与fork函数的功能差不多,都是创建一个新的进程。但是与fork不同的是,vfork并不将父进程的地址空间完全复制到子进程,因为子进程会立即调用exec或exit,这样子进程就不会访问该父进程的地址空间;在exec或exit之前,子进程在父进程的空间运行,父进程要等到子进程exec或exit之后才运行,这一点与fork有很大的不同。

三.等待进程退出

     当一个进程正常或异常终止时,内核会向其父进程发送SIGCHLD信号。父进程可以调用wait、waitpid等函数获取子进程的退出状态。

     1. wait函数 

      #include <sys/wait.h>

      pid_t wait(int *statloc);  // 返回成功时,返回子进程的ID;否则-1

      wait函数阻塞直到子进程退出;当有多个子进程时,只要有一个子进程退出,wait就返回,且将终止进程的终止状态就存在它所指向的单元中。

      2. waitpid函数

      #include <sys/wait.h>

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

      waitpid函数等待一个指定的进程退出,同时根据设置的options,可以阻塞或立即返回,默认条件下,是阻塞的。

           pid == -1  等待任一子进程的退出;

           pid>0  等待进程ID = pid的子进程;

           pid == 0 等待组ID=调用进程组ID的任一子进程;

           pid < -1  等待其组ID等于pid绝对值的任一子进程。

     options的值有三个WCONTINUED、WNOHANG、WUNTRACED,常用的为WNOHANG(pid指定的子进程不是立即可用时,waitpid不阻塞,立即返回且返回值为0)。

     对于进程退出状态的获取,unix定义了6个宏,都已WIF开头,这里暂不介绍。

四.exec族函数

    fork函数创建了调用进程的一份拷贝,但大多数应用都需要子进程执行与父进程不一样的代码,exec函数族用新的程序映像覆盖了子进程的映像。fork-exec:子进程执行新的程序,父进程继续执行原来的代码。

       exec函数族总共有6个函数,以l、v、p结尾来区分不同,l代表接受的输入参数都是独立的参数;v代表接受的输入参数为一个数组;p代表输入参数接受路径;无p,输入参数接受文件名。

  第一个区别是:

       无p,取路径名做为参数;有p,取文件名做为参数,如果文件名中不包含 “/” 则从PATH环境变量中搜寻可执行文件, 如果找到了一个可执行文件,但是该文件不是连接编辑程序产生的可执行代码文件,则当做shell脚本处理。

第二个区别:

      " l "代表 list即表 ,而" v "代表 vector即矢量,以list的形式给出的参数,最后要加一个空指针,如果用常数0来表示空指针,则必须将它强行转换成字符指针,否则有可能出错。以矢量的形式给出的参数接受数组参数。

最后一个区别:

       与向新程序传递环境变量有关,以e结尾的函数,可以向函数传递一个指向环境字符串指针数组的指针。即自个定义各个环境变量,而其它四个则使用进程中的环境变量。

#include <unistd.h> extern char **environ; int execl(const char *path, const char *arg,..., ); int execlp(const char *file, const char *arg,..., ); int execle(const char *path, const char *arg,..., ); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]);

实例:

(1)在平时的编程中,如果用到了exec函数族,一定记得要加错误判断语句。先判断execl的返回值,如果出错,可以用perror( )函数打印出错误信息。

如:if (execl(“path”,”..””(char *)0) < 0)

    {

       perror(“execl error!”);

   }

如果调用出错,可输出:execl error!: 错误原因   这样可方便查找出错原因

(2)注意下面书写格式:

先定义一个指针数组:char *argv[]={“ls”,”-l”,(char *)0}

用execv调用ls:    execv(“/bin/ls”,argv)

 如果用execvp

execvp(“ls”,argv)      //直接写ls就可以了,因为会自动搜索PATH路径下的相应可执行程序