您的位置:首页 > 其它

进程创建与相关函数等知识点总结

2017-02-06 15:46 246 查看

1、进程基本概念:

进程:一个在内存中运行的程序,我们叫进程。

(1)、进程常见状态:

S:休眠状态Sleep(省资源)

s:表该进程有子进程

O:可运行状态

R:正在运行状态

Z:僵尸进程(僵尸状态),已经结束但资源没有完全回收

(2)、Linux中查看进程:

ps:查看本终端启动的进程(一个shell一个ps):



②ps -aux:Linux专用查看进程(ps -aux | more),UNIX不直接支持该方法(间接支持:/usr/ucb/ps -aux即可),因为UNIX下ps的PATH路径只包括/usr/bin/ps,它不支持ps -aux。但/usr/ucb/ps支持。而Linux下ps只有/usr/bin/ps一个路径。



图中STAT(状态)基本都是S(休眠状态),并且init(pid=1)进程有子进程(s)

⑤ps -ef:Unix/Linux通用的查看进程方式(ps -ef | more)



明显ps -ef 比ps -aux的属性更少。

杀死进程:

kill -9 pid:杀死pid对应的进程(给pid进程发送信号9)。

(3)、父子进程的关系:

①在宏观上,父子进程同时运行(因为时间片很小)。

②如果子进程先结束,子进程会给父进程发送一个信号,由父进程回收子进程的相关资源。

③如果父进程先结束,会给自己的父进程福信号,但子进程不会被父进程回收资源。成为孤儿进程,init(pid=1)成为孤儿进程的父进程,init则负责回收孤儿进程运行完毕的资源。

④子进程先结束,同时发送信号但父进程没有收到,或者子进程根本就没有发送信号,(子进程在父进程结束以前结束了不会成为孤儿进程,但资源没被回收),子进程就变成了僵尸进程。

进程ID:

每个进程都有一个非负整型表示的唯一进程ID。ID唯一但可以重用,当一个进程正常终止后,其原占ID就可以再次使用(延迟重用)。

getuid获取实际用户id、geteuid获取有效用户id;

getgid获取实际组id、getegid获取有效id;

(对于实际编程来说,实际id不重要,有效id重要)

setuid、setgid则用来设置uid与组id。

2、fork()函数:

fork()通过复制自身(父进程)创建新进程(子进程)。并非完全复制,子进程会复制父进程代码区之外的内存区域(物理内存的数据&虚拟内存的地址),即和父进程共享代码区(因为代码区是只读的,所以可以共享)。而虚拟内存各占独立一套,内核也是共享的,但是父子进程都没有内核的操作权限(也就是说内核管理父子进程,父子进程PCB在内核中),如图所示:



fork()之后父子进程同时运行,但谁先执行谁后执行在不同操作系统中的调度算法不尽相同(因为标准中没有规定),根据时间片等来判断。如果在fork()之后公共代码(都会执行的代码)中对文件加写锁,后执行的进程会加锁失败等问题。

fork()复制文件描述符:

在fork()之前打开的文件,其文件描述符会被复制,而fork()之后打开的文件,其文件描述符不会被复制,只是父子进程各自打开。复制文件描述符时,只复制描述符,不复制文件表,即两个进程共用一张文件表,一个偏移量,父子进程修改文件不会覆盖,只会追加。

fork()父子进程共享文件表项,如图所示:



其使用方法与注意事项可参考博客: 进程创建与fork()的恩怨情仇

3、退出进程的方法与exit()函数:

正常终止进程的五种方式:

①main中调用子进程运行完毕后加一个return

②执行exit(int status)函数,将status & 03777的结果返回给父进程。

③调用_exit(int status)或者_Exit(int status)函数

④进程的最后一个线程执行了返回语句

⑤进程的最后一线程调用pthread_exit()函数

非正常结束:

信号结束方式;

被其他进程取消最后一个线程。

_exit()、_Exit()、exit()的区别:

_exit()与_Exit()这俩函数功能一样,第一个是UC函数(unistd.h)第二个是标C函数(stdlib.h),调用之后立即结束。参数为整数类型,负数代表非正常退出。_Exit()与exit()函数都是标准C函数,他们的区别是:exit()并不是立即退出,在退出之前会调用某些函数,只要用atexit(参数是函数指针)注册,退出之前就会被调用(即使不调用exit()正常return也会先调用atexit()注册的函数,而_Exit()则不会)。_Exit()则是立即退出,不会做任何额外的事。如果不是特别紧要,一般都调用exit()即可,在UC编程中,调用_exit()也可以。

4、wait()与waitpid()函数:

父进程等待子进程结束后再执行退出的方法:(进程之间的调度)

函数wait()和waitpid()函数。

#include<sys/wait.h>
pid_t wait(int * status);/*传出参数,传出子进程结束的状态*/
pid_t waitpid(pid_t idtype,id_t id,siginfo_t * infop,int options);


(1)、wait()函数:

wait()函数用于父进程等待子进程的结束,子进程一旦结束,父进程也立即结束,否则wait()一直等待处于阻塞状态。如果父进程有多个子进程,则等待任意一个结束就返回,返回结束的子进程id,传出参数(也是返回)为传出结束的子进程的状态和退出码。wait()也可以回收僵尸进程,因此可使用wait()防止僵尸进程产生。

宏函数:

WIFEXITED(status):判断是否正常结束(正常结束返回真);

WIFSIGNALED(status):判断是否非正常结束(非正常结束返回真,接到一个不捕捉的信号);

WTERMSIG(status):获取使子进程非正常终止的信号编号;

WEXITSTATUS(status):获取退出码(即exit的参数),后八位为有效(0~255)。

(2)、waitpid()函数:

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

/*waitpid可以完全代替wait;

waitpid(-1,&status,0); 等价于 wait(&status);*/

解析: wait只能等待第一个结束的便停止等待,而waitpid则可以指定(第一个参数pid)需要进行等待的子进程id;第二个参数与wait()相同。

参数:

①pid:

pid<-1:等待uid=|pid|进程组的子进程(负数代表的是进程组,取绝对值是其组id);

pid==-1:等待任意子进程;

pid==0:等待和父进程同一进程组的子进程;

pid>0:等待的子进程id为参数pid(指定子进程,其它三种不指定特定pid的子进程)。

②statu:

功能和wait()是一样的

③options:

可取值为:WNOHANG(wait no hang),等待不挂起,没有子进程结束也直接结束,不会等待(父进程非阻塞意义不大);一般options直接置零即可(置零为阻塞等待)。

返回值:

①成功(有子进程结束)返回结束的子进程pid;

②如果设置options为WNOHANG时没有子进程退出结束就返回0;

③失败返回-1。

5、vfork()与exec()系列函数:

(1)、vfork()函数:

pid_d vfork(void);/*just like fork()*/


与fork()的区别:

①vfork()不复制父进程任何的内存空间

②vfork()确保子进程优先执行

vfork()创建的子进程占用父进程的内存空间运行。父进程在此时是阻塞的。

vfork()要和exec系列函数联合使用采用意义:vfork()负责创建子进程,而exec系列函数负责提供新的程序被执行。当vfork()创建的子进程执行新的程序时,父进程的内存空间就会被返回给父进程,父进程不再阻塞,父子进程同时运行。

③vfork()在调用exit()或者exec()之前父进程处于阻塞,如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

经验:fork()创建的进程和父进程执行相同代码;当然fork()也可以与exec系列函数结合使用,但是我们不这样做(因为fork已经复制了与父进程独立的内存空间,不需要再exec画蛇添足、狗尾续貂了)

vfork()在有意的前提下,执行的代码与父进程无关,而是全新的代码;如果用vfork创建新的子进程与父进程执行相同的代码(选择的不同分支),还不如直接使用fork()。

(2)、exec系列函数:

#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, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *file, char *const argv[], char * const envp[]);


exec本身不是函数,exec系列函数不是新建一个进程(不改变pid),而使用新的代码区堆区栈区以及数据区等替换旧的内存区(新程序修改就程序)。我们以execl系列为重点:

/**掌握execl系列前两个即可,p表示path,e表示env**/

#incldue<unistd.h>
int execl(const char * path,const char *arg,...);
int execlp(const char * file,const char *arg,...);/*不需要加PATH路径*/

int execle(const char * path,const char *arg,...,char * const envp[]);


前两个用法:execl(” “, ” “, ” “, …, NULL);

第一个参数是可执行程序所在路径以及文件名(第二个函数可不加路径)

第二个参数是可执行程序文件名

后面的参数就是选项/参数



最后一个参数是NULL。以NULL为结束(必须),表示有效参数就NULL前几个。

这种形式是调用系统路径下的命令程序,如果需要调用自己写的代码程序,则一般不需要参数与选项,需要则按规则加上即可。

eg:execl("/bin/ls","ls","-la","/root",NULL);
/*调用系统程序,当然也可以调用自己写的程序*/


举例如下图所示(只测试execl()函数不加vfork()):

测试代码:



测试结果:



结果分析:



注意: exec系列函数会更换代码区(代码区跳转,但不会在跳转回来),所以在同一进程中,exec系列函数后面的任何内容都不会执行。故在exec之后不需要加exit()。并且在加exec系列函数之后父子进程便遵循fork()的运行规则。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unix