进程标识
每个进程都有一个非负整数表示的唯一进程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 */
};