UNIX进程控制(二)

进程标识

  每个进程都有一个非负整数表示的唯一进程ID。常将其用作其他标识符的一部分以保证其唯一性。例如:应用程序有时就把进程ID作为名字的一部分来创建一个唯一的文件名。
  系统中有一些专用进程,但具体细节随实现而不同。ID 0的进程通常是调度进程,常常被称为交换进程(swapper)。该进程是内核的一部分,他不执行任何磁盘上的程序,所以称为系统进程。进程ID 1通常是init进程…
  每个UNIX系统实现都有他自己的一套提供操作系统服务的内核进程,例如:某些UNIX的虚拟存储器实现中,进程ID 2是页守护进程,负责支持虚拟存储器系统的分页操作。
除了进程ID,每个进程还有一些其他的标识符,下列函数返回这些标识符。

include<unistd.h>
pid_t getpid(void);						//返回值:调用进程的进程ID
pid_t getppid(void);					//返回值:调用进程的父进程ID
uid_t getuid(void);						//返回值:调用进程的实际用户ID
uid_t geteuid(void);					//返回值:调用进程的有效用户ID
gid_t getgid(void);						//返回值:调用进程的实际组ID
gid_t getegid(void);					//返回值:调用进程的有效组ID

注意:这些函数都没有出错返回。

函数fork

#include<unistd.h>

pid_t fork(void);

功能:一个现有的进程可以通过fork函数创建新进程

返回值:子进程返回0,父进程返回子进程ID;若出错,返回-1

注意:子进程是父进程的副本。子进程获得父进程的数据空间、堆、栈、I/O流(共享文件指针和文件描述符)、缓冲区的拷贝等,与父进程共享代码段

int main(void)  
{  
   int i=0;  
   for(i=0;i<3;i++){  
       pid_t fpid=fork();  
       if(fpid==0)  
           printf("son/n");  
       else  
           printf("father/n");  
   }  
   return 0;  
  
}  

这里就不做详细的解释了,只做一个大概分析。
在这里插入图片描述
fork通常有以下两种用法:
1、一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。这在网络服务进程中是常见的
——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理该请求。父进程则继续等待下一个服务请求。
2、一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

vfork函数

vfork函数与fork函数功能基本一致。
区别:
1、通过vfork创建的进程不复制父进程的地址空间(数据段、BSS段、堆、栈、I/O流(共享文件指针和文件描述符)、缓冲区),因为子进程会立即调用exec。
2、vfork保证子进程先运行,在它调用exec或exit之后父进程才能被调度运行,当子进程调用这两个函数中的任意一个时,父进程会恢复运行。

exec系列函数

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

这些函数之间的第一个区别是前四个函数取路径名作为参数,后两个函数取文件名作为参数,最后一个取文件描述符作为参数。
在很多UNIX实现中,这7个函数只有execve是内核系统调用。另外六个是库函数,他们最终都要调用该系统调用。这七个函数之间的关系如下图
在这里插入图片描述
exec函数实例:

#include<stdio.h>
#include<sys/wait.h>
#include<unistd.h>
char* env_init[] = {"USER=unknow","PATH=/temp",NULL};
int main()
{
	pid_t pid;
	if(pid=fork()<0)
	{
		err_sys("fork error");
	}
	else if(pid==0)
	{
		if(execle("/home/sar/bin/echoall","echoall","myarg1","MY ARG2",(char *)0,env_init)<0)
		{
			err_sys("execle error");
		}
		if(waitpid(pid,NULL,0)<0)
		{
			err_sys("wait error");
		}
		if(pid=fork()<0)
		{
			err_sys("fork error");
		}else if(pid==0)
		{
			if(execlp("echoall","echoall","only 1 arg",(char *)0)<0)
			{
				err_sys("execlp error");
			}
		}
	}
	exit(0);
}

函数wait和waitpid

当一个进程正常或异常终止时,内核就向其父亲发送SIGCHLD信号。因为子进程终止是异步事件,所以这种信号也是内核向父进程发的异步通知。

调用wait或waitpid函数的进程会发生什么?
● 如果其所有进程都还在运行,则阻塞。
● 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
●如果它没有任何进程,则立即出错返回。

pid_t wait(int *statloc);
功能:等待所有子进程结束 ,并获取到最终的状态码。只要有一个进程结束就立即返回。

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

功能:等待指定的进程结束,并获取到最终的状态码。

statloc是一个整形指针。如果statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元里。如果不关心终止状态,可将参数指定为空指针。

POSIX.1规定,终止状态用定义在<sys/wait.h>中的各个宏来查看,有4个互斥的宏可用来取得进程终止的原因,它们的名字都以WIF开始。

说明
WIFEXITED 若为正常终止子进程返回的状态,则为真。对于这种情况,可执行WEXITSTATUS,获取子进程传送给exit或_exit参数的低8位
WIFSIGNALLED 若为异常终止子进程返回的状态,则为真
WIFSTOPPED 若为当前暂停子进程的返回的状态,则为真
WIFCONTINUED 若在作业控制暂停后已经继续的子进程返回了状态,则为真

关于waitpid函数中pid参数的作用解释

pid==-1 等待任一子进程,此种情况下,waitpid与wait等效
pid>0 等待进程ID与pid相等的子进程
pid==0 等待组ID等于调用进程组ID的任一子进程
pid<-1 等待组ID等于pid绝对值的任一子进程

options参数使我们能进一步控制waitpid的操作

常量 说明
WCONTINUED 由pid指定的子进程停止后已经继续,但其状态尚未报告,则返回其状态
WNOHANG 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时返回0
WUNTRACED 由pid指定的子进程已停止,但其状态尚未报告,则返回其状态

这两个函数的区别:
1、子一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。
2、waitpid并不等待在其调用之后的第一个终止子进程,他又若干个选项,可控制所等待的进程。

函数system

int system(const char *cmdstring)

功能:执行系统命令,也可以加载可执行程序。

因为system在实现中调用了fork、exec、waitpid,所以有3种返回值
(1)、fork失败或者waitpid返回除EINTR之外的出错,则sysytem返回-1,并且设置errno以指示错误类型。
(2)、如果exec失败,则其返回值如同shell执行了exit(127)一样。
(3)、三个函数都成功,那么返回值是shell的终止状态。

进程终止

有8种方式使进程终止,其中5种为正常终止,它们是:
(1)、从main函数返回;
(2)、调用exit函数;
(3)、调用_exit或_Exit;
(4)、最后一个线程从其启动例程返回;
(5)、从最后一个线程调用pthread_exit;
异常终止有3种方式,它们是
(6)、调用abort;
(7)、接收到一个信号;
(8)、最后一个线程对取消请求做出响应。

void exit(int status);

功能:调用者立即结束该进程

status:退出状态码,可以在父进程中获取到,子进程留给父进程的遗言

退出前会做的事情:
  1、先调用所有事先注册的函数(通过 atexit/on_exit 注册的)
    int atexit(void (*function)(void));
     功能:注册一个函数,当进程通过exit函数结束时调用
     function:函数指针,无返回值无参数
     返回值:成功为0,失败-1
     int on_exit(void (*function)(int , void *), void *arg);
     功能:注册一个函数,当进程通过exit函数开始结束时调用
     function:函数指针,无返回值,参数1为exit函数的参数,参数2为on_exit的第二个参数。
     arg:当function函数被调用是传递给它第二参数
   2、冲刷所有处在未关闭状态的标准I/O流
   3、返回一个整数( 0 (EXIT_SUCCESS) / 1 (EXIT_FAILURE) )给操作系统
   4、该函数不会返回,它的功能实现借助了_exit/_Exit
注意:atexit 和 on_exit 注册的函数都会加入同一个栈结构中,谁最后登记的谁最先执行,他们的区别只有注册的函数格式不同。

#include <unistd.h>

void _exit(int status);

#include <stdlib.h>

void _Exit(int status);//调用了系统的_exit(为了兼容)

功能:调用的进程会结束,没有返回值。

status:会被父进程获取到(低八位,一个字节)。

说明:
   1、进程结束前会关闭所有处于打开状态的文件描述符
   2、把所有的子进程托付给孤儿院init
   3、向它的父进程发送SIGCHLD信号。
注意:exit函数也会执行以上操作,因为它底层调用了_exit/_Exit

进程时间

任一进程都可以调用times函数获得它自己以及已终止子进程的上述值。

#include <sys/times.h>

clock_t times(struct tms *buf);

此函数填写由buf指向的tms结构,该结构定义如下

struct tms {
               clock_t tms_utime;  /* user time */
               clock_t tms_stime;  /* system time */
               clock_t tms_cutime; /* user time of children */
               clock_t tms_cstime; /* system time of children */
           };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章