进程:一个在内存中运行的程序,我们叫进程。
S:休眠状态Sleep(省资源) s:表该进程有子进程 O:可运行状态 R:正在运行状态 Z:僵尸进程(僵尸状态),已经结束但资源没有完全回收
①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)。
①在宏观上,父子进程同时运行(因为时间片很小)。 ②如果子进程先结束,子进程会给父进程发送一个信号,由父进程回收子进程的相关资源。 ③如果父进程先结束,会给自己的父进程福信号,但子进程不会被父进程回收资源。成为孤儿进程,init(pid=1)成为孤儿进程的父进程,init则负责回收孤儿进程运行完毕的资源。 ④子进程先结束,同时发送信号但父进程没有收到,或者子进程根本就没有发送信号,(子进程在父进程结束以前结束了不会成为孤儿进程,但资源没被回收),子进程就变成了僵尸进程。
进程ID:
每个进程都有一个非负整型表示的唯一进程ID。ID唯一但可以重用,当一个进程正常终止后,其原占ID就可以再次使用(延迟重用)。 getuid获取实际用户id、geteuid获取有效用户id; getgid获取实际组id、getegid获取有效id; (对于实际编程来说,实际id不重要,有效id重要) setuid、setgid则用来设置uid与组id。
fork()通过复制自身(父进程)创建新进程(子进程)。并非完全复制,子进程会复制父进程代码区之外的内存区域(物理内存的数据&虚拟内存的地址),即和父进程共享代码区(因为代码区是只读的,所以可以共享)。而虚拟内存各占独立一套,如图所示:
fork()之后父子进程同时运行,但谁先执行谁后执行在不同操作系统中的调度算法不尽相同(因为标准中没有规定),根据时间片等来判断。如果在fork()之后公共代码(都会执行的代码)中对文件加写锁,后执行的进程会加锁失败等问题。
fork()复制文件描述符: 在fork()之前打开的文件,其文件描述符会被复制,而fork()之后打开的文件,其文件描述符不会被复制,只是父子进程各自打开。复制文件描述符时,只复制描述符,不复制文件表,即两个进程共用一张文件表,一个偏移量,父子进程修改文件不会覆盖,只会追加。
fork()父子进程共享文件表项,如图所示:
其使用方法与注意事项可参考博客: 进程创建与fork()的恩怨情仇
正常终止进程的五种方式: ①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()也可以。
父进程等待子进程结束后再执行退出的方法:(进程之间的调度) 函数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);wait()函数用于父进程等待子进程的结束,子进程一旦结束,父进程也立即结束,否则wait()一直等待处于阻塞状态。如果父进程有多个子进程,则等待任意一个结束就返回,返回结束的子进程id,传出参数(也是返回)为传出结束的子进程的状态和退出码。wait()也可以回收僵尸进程,因此可使用wait()防止僵尸进程产生。
宏函数: WIFEXITED(status):判断是否正常结束(正常结束返回真); WIFSIGNALED(status):判断是否非正常结束(非正常结束返回真,接到一个不捕捉的信号); WTERMSIG(status):获取使子进程非正常终止的信号编号; WEXITSTATUS(status):获取退出码(即exit的参数),后八位为有效(0~255)。
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。
与fork()的区别:
①vfork()不复制父进程任何的内存空间 ②vfork()确保子进程优先执行 vfork()创建的子进程占用父进程的内存空间运行。父进程在此时是阻塞的。 vfork()要和exec系列函数联合使用采用意义:vfork()负责创建子进程,而exec系列函数负责提供新的程序被执行。当vfork()创建的子进程执行新的程序时,父进程的内存空间就会被返回给父进程,父进程不再阻塞,父子进程同时运行。 ③vfork()在调用exit()或者exec()之前父进程处于阻塞,如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
经验:fork()创建的进程和父进程执行相同代码;当然fork()也可以与exec系列函数结合使用,但是我们不这样做(因为fork已经复制了与父进程独立的内存空间,不需要再exec画蛇添足、狗尾续貂了) vfork()在有意的前提下,执行的代码与父进程无关,而是全新的代码;如果用vfork创建新的子进程与父进程执行相同的代码(选择的不同分支),还不如直接使用fork()。
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("ls","ls","-la","/root",NULL);/*调用系统程序,当然也可以调用自己写的程序*/注意: exec系列函数会更换代码区(代码区跳转,但不会在跳转回来),所以在同一进程中,exec系列函数后面的任何内容都不会执行。故在exec之后不需要加exit()。并且在加exec系列函数之后父子进程便遵循fork()的运行规则。
新闻热点
疑难解答