(一) 进程的创建
pid_t fork(void); #include <sys/types.h> #include <unistd.h> fork完全复制一个进程作为子进程(copy-on-write),The child process and the parent process run in separate memory spaces. At the time of fork() both memory spaces have the same content. Memory writes, file mappings (mmap(2)), and unmappings (munmap(2)) performed by one of the processes do not affect the other. 不同之处:man手册整理,部分内容,详见man手册 (1)子进程有自己的pid,并且如果没有用setpgid ()设置进程组id,默认为自己pid (2)子进程不会继承父进程的memory locks (mlock(2), mlockall(2)) (3)子进程的进程资源利用率(getrusage(2)) 和CPU时间计数器(times(2))被初始化为0 (4)子进程的未决信号集(set of pending signals)被初始化为0。(sigpending(2)) (5)子进程不会继承父进程的信号量机制。(semop(2)) … … 返回值:成功,父进程中,该函数返回子进程的pid;子进程中,该函数返回0 失败,父进程中,该函数返回-1,errno被设置,子进程没有被创建,也就不存在返回不返回 t_stdio.h
#ifndef __T_STDIO_H__
#define __T_STDIO_H__
#include <stdio.h>
#define E_MSG(STR,VAL) do{\
perror(STR);\
return (VAL);\
}while(0)
#endif
fork1.c
#include <t_stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc
, char *argv
[]){
if(argc
< 2){
printf("缺少参数\n");
return -1;
}
int num
= atoi(argv
[1]);
for(int i
=0; i
<num
; i
++){
pid_t pid
= fork();
if(pid
== -1) E_MSG("fork", -1);
if(pid
== 0){
printf("process child %d...\n", i
+1);
getchar();
break;
}else{
if(i
==num
-1){
printf("wait for %d child process...\n", i
+1);
for(int j
=0; j
<num
; j
++){
wait(NULL);
}
}
}
}
return 0;
}
运行效果:假设输入为3,则父进程会创建3个子进程,需要getchar()三次才会退出 fork2.c
#include <t_stdio.h>
#include <unistd.h>
#include <stdlib.h>` `
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc
, char *argv
[]){
if(argc
< 2){
printf("缺少参数\n");
return -1;
}
int num
= atoi(argv
[1]);
for(int i
=0; i
<num
; i
++){
pid_t pid
= fork();
if(pid
== -1) E_MSG("fork", -1);
if(pid
== 0){
printf("process child %d...\n", i
+1);
if(i
==num
-1){
getchar();
}
}else{
wait(NULL);
break;
}
}
return 0;
}
运行效果:输入a.out 3 回车,会在父进程的基础上,创建3层孩子,共4层。再按回车后,结束最后一个子进程,然后从最后一个子进程开始,向上层递进,一个一个终止。(详情见下,进程的终止,wait()的用法)
(二) 进程的正常终止(terminate)
概念 (1)main函数return返回,代表进程的终止。(官方定义,但实际上main是由start调用,也并非是完全意义上的终止) (2)调用exit(3),代表整个进程的终止 注:如果return用于非main函数,表示函数的终止,而非进程的终止void exit(int status); #include <stdlib.h> 功能: 用于进程的正常终止 参数:将status & 0377(即屏蔽高24位,保留低8位),传递给父进程,父进程有专门函数接收这个值,会在下面的进程资源回收中介绍 返回值:无
#include <stdlib.h>
int main(void){
exit(99);
return 0;
}
运行效果,执行结束后,在bash执行命令 echo $?即显示进程结束后返回给bash的值,如果把exit注释掉,让main函数以return结束,则bash接收到的值为0.
(三)遗言函数
概念:进程终止以后才调用的函数,即main结束后调用的函数。在进程正常的情况下向进程注册遗言函数,遗言函数会在进程终止的时候被调用。int atexit(void (*function)(void)); #include <stdlib.h> 参数:function,遗言函数的入口地址,遗言函数的参数和返回值都为void 返回值:成功返回0,错误返回非0 详细说明见man手册: (1)Functions so registered are called in the reverse order of their registration (2)The same function may be registered multiple times: it is called once for each registration. (3)When a child process is created via fork(2), it inherits copies of its parent’s registrations. (4)Upon a successful call to one of the exec(3) functions, all registrations are removed.
#include <stdio.h>
#include <stdlib.h>
void death(void){
printf("i will be back1\n");
}
int main(void){
int a
= atexit(death
);
if (!a
)
printf("register success...\n");
return 0;
}
执行效果:register success...
i will be back1
int on_exit(void (*function)(int , void *), void *arg); #include <stdlib.h> 参数:函数入口地址,函数没有返回值,参数为int和一个指针。 返回值:成功0,失败非0 对于参数function的说明:The function is passed the status argument given to the last call to exit(3) and the arg argument from on_exit().这个function被传入最近一次调用exit传给exit的status和来自on_exit的参数arg,即把最近一次调用时,传给exit的值,也传给function一份,遗言函数的参数arg,也传给function,显然,status是没有和0377做&的
#include <stdio.h>
#include <stdlib.h>
void death(int n
,void *arg
){
printf("%s\n",(char *)arg
);
printf("exit code:%d\n",n
);
return;
}
int main(void){
on_exit(death
,"I will be back");
printf("输入任意字符结束进程\n");
getchar();
exit(-1);
}
执行结果:输入任意字符结束进程
I will be back
exit code:-1
(四) 进程资源的回收
相关概念: 进程终止的时候,需要将进程占用的资源回收 父进程负责回收子进程的资源 僵尸进程,就是进程已经终止了,但是进程的资源没有被回收 孤儿进程,父进程已经终止了,子进程还在,在这样的情况下,需要将子进程过继给另一个进程pid_t wait(int *wstatus); #include <sys/types.h> #include <sys/wait.h> 功能:阻塞当前进程,等待任一子进程的终止(terminate), 将子进程main函数返回值或者子进程中exit()传给父进程的值存储到wstatus这个地址上。 参数:wstatus,用来存储子进程给父进程的值,可以为NULL,为NULL表示不存储来自子进程的值 返回值:成功,返回已经结束的子进程的pid;出错返回-1,errno被设置 详解:在父进程中,可以用以下宏来检测终止的子程序返回来的值,注意,宏的参数不是指针,是int整型数
#include <t_stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void){
int s
;
pid_t pid
=fork();
if(pid
==-1)E_MSG("fork",-1);
if(pid
==0){
printf("child process...%d\n",getpid());
getchar();
exit(-1);
}else{
wait(&s
);
if(WIFEXITED(s
))
printf("exit code...%d\n",\
WEXITSTATUS(s
));
if(WIFSIGNALED(s
))
printf("signum:%d\n",WTERMSIG(s
));
printf("process parent...%d\n",getpid());
}
return 0;
}
pid_t waitpid(pid_t pid, int *wstatus, int options); #include <sys/types.h> #include <sys/wait.h> 功能:阻塞等待pid指定的子进程的change state A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a signal. 参数: pid <-1:等待任一进程组id和形参pid的绝对值相等的子进程 -1:等待任意子进程 0:等待和当前进程同一个进程组的子进程 >0:指定了要等待的子进程的pid 进程组中有多个进程,每个进程组都有一个leader,进程组的组id是这个leader的pid,默认 的情况下,创建的子进程和父进程属于同一个进程组 wstatus 和wait函数一样 options WNOHANG:如果没有子进程终止,立即返回 0:阻塞等待子进程的终止,其他值详见man 返回值:成功返回状态改变的子进程的pid,如果 WNOHANG被指定,没有子进程的状态的改变,返 回0;错误,-1,errno被设置 wait(&status); == waitpid(-1, &status, 0);
#include <t_stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void){
int s
;
pid_t pid
=fork();
if(pid
==-1)E_MSG("fork",-1);
if(pid
==0){
printf("child process...%d\n",getpid());
getchar();
exit(-1);
}else{
int w
=waitpid(-1,&s
,WNOHANG
);
if(w
==0){
printf("no child termination\n");
return -1;
}
if(WIFEXITED(s
))
printf("exit code...%d\n",\
WEXITSTATUS(s
));
if(WIFSIGNALED(s
))
printf("signum:%d\n",WTERMSIG(s
));
printf("process parent...%d\n",getpid());
}
return 0;
}
(五)进程映像的更新
系统调用 int execve(const char *filename, char *const argv[],char *const envp[]); #include <unistd.h> 功能:执行一个程序 参数: filename:指定了要执行的文件的名字;这个文件必须是二进制可执行文件或脚本文件 argv:字符串数组,传递给main函数的参数,习惯上第一个是可执行文件的名字,以NULL结尾 envp:name = value,作为环境变量传递给新的可执行程序,以NULL结尾 成功不返回,错误,-1,errno被设置exec(3)族 #include <unistd.h> extern char **environ; 指向当前进程的环境变量列表 int execl(const char *path, const char arg, … / (char *) NULL */); int execlp(const char *file, const char arg, … / (char *) NULL */); int execle(const char *path, const char arg, … /, (char *) NULL, char * const envp[] */); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]); exec 公有的 l l代表list,一个一个列出来,而非二维数组,最后一个要为NULL v v代表vector p PATH环境变量 需要知道要加载的可执行程序所在的路径。 如果有p字母,会到环境变量PATH指定的路径中找可执行程序, 如果没有p字母,必须指定可执行文件的所在路径(相对路径或者绝对路径) e 没有字母e.代表从父进程继承环境变量;如果有字母e,用户指定环境变量传递给新的可执行程序. 实例1
#include <t_stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void){
char *ps_argv
[]={"ps","-o","pid,ppid,pgrp,comm",NULL};
pid_t pid
=fork();
if(pid
==-1)E_MSG("fork",-1);
if(pid
==0){
printf("child process...\n");
execlp("ps","ps","-o","pid,ppid,pgrp,comm",NULL);
perror("execvp");
exit(0);
}else{
wait(NULL);
}
return 0;
}
首先把下面这段代码编译成可执行文件 t_env
#include <stdio.h>
extern char **environ
;
int main(void){
for(int i
=0;environ
[i
]!=NULL;i
++)
printf("%s\n",environ
[i
]);
return 0;
}
在同一目录下编译以下代码,并执行
#include <t_stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void){
char *ps_envp
[]={"caption=beijing","name=tarena",NULL};
pid_t pid
=fork();
if(pid
==-1)E_MSG("fork",-1);
if(pid
==0){
printf("child process...\n");
execle("./t_env","t_env",NULL,ps_envp
);
perror("execvp");
exit(0);
}else{
wait(NULL);
}
return 0;
}