获取进程ID
每个进程都有一个唯一的正数进程ID,getpid函数返回调用进程的PID,getppid函数返回它的父进程的PID(创建调用进程的进程)
创建和终止进程
1.运行
2.停止
3.终止
1 | int main() |
要点:
1.fork函数只调用一次,但是会返回两次,一次是在父进程中,另外一次是在子进程中。
2.父进程总会返回子进程的PID,在子进程当中,fork总会返回0
3.并发执行,内核能够一任意方式交替执行它们逻辑控制流中的指令
在x86上,先完成父进程,再完成子进程,这个顺序不同的系统会不同,画出进程图,所有拓扑排序序列都可以。
4.相同但是独立的地址空间,每个进程都有相同的用户栈,相同的本地变量值,堆等“环境”,但却是独立的,就是说一个进程改变内部的环境不会影响另外一个进程
5.共享文件共享文件
结果如下
所以上面程序的分析:
pid==0,即子进程
那如果是以下程序呢?
解析:
1 |
|
回收子进程
当一个进程由于某种原因中止的时候,内核不会立刻把它从系统中清除,而是保存在一个已经终止的状态中,等待被父进程回收。
所以一个终止了的但是还没有被回收的进程,成为僵死进程
如果父进程终止了,内核会安排init进程成为它孤儿进程的养父,init进程的PID位1,是系统启动的时候由内核创建的,不会终止,是所有进程的祖先。
1 | pid_t waitpid(pid_t pid,int *statsup,int options){ |
pid参数用来判断等待集合的成员。
如果pid>0,那么等待集合就是一个单独的子进程,它的进程ID等于pid
如果pid=-1,那么等待集合是由父进程的所有子进程组成的
wait函数
wait函数是waitpid函数的简单版本
1 | pid_t wait(int *statusp); // = waitpid(-1,&status,0); |
让进程休眠
//返回要休眠的秒数1
unsigned int sleep(unsigned int secs);
1 | int pause (void) |
加载并运行程序
execve函数在当前进程上下文当中加载并且运行一个新程序1
int execve(const char *filename,const char *argv[],const char *envp[])
execve函数加载并且运行可执行目标文件,且带参数列表argv和环境变量envp,只有当出现错误例如找不到filename的时候,execve才会返回到调用程序。
execve函数调用一次,而且从不返回
当execve加载了filename后,启动代码设置栈,并将控制传递给新程序的主函数。
1 | int main(int argc,char *argv,char **envp) |
1.argc argv[]数组中非空指针的数量
2.argv argb[]数组中的第一个条目
3.envp 指向argv[]数组的第一个条目
1 | char *getenv(const char *name) 函数 |
1 | int setenv(const char *name,const char *newvalue,int overwrite)//当overwrite非0的时候,如果name不存在,那么setenv把“name=newvalue"添加到数组当中 |
1 | //查看函数的命令行参数和环境变量 |
用户栈的组织结构
动态链接器变量下的envp,argv,与argc是libc_start_main的栈帧
信号
def:一条通知进程系统发生了一个某种类型事件的小信息
传送一个信号到目的进程由两个不同步骤组成:
发送信号
进程组:
每个进程属于一个进程组
1 | pid_t getpgrp(void); return id of id group |
1 | int setpgid(pid_t pid,pit_t pgid); |
用/bin/kill发送信号
1 | /bin/kill -9 15213 |
从键盘发射信号
作业(job)用来表示为一条命令行求值而创建的进程,在任何时候,至多只有一个前台作业和0个或多个后台作业。
1 | ls | sort //会创建一个由两个进程组成的前台作业 |
用kill发射信号
1 | int kill (pid_t pid,int sig); |
接收信号
1.内核位每个进程在pending位向量维护着待处理信号的集合,而在blocked位向量维护着被阻塞的信号集合。所以任何时刻一种类型的信号只会被接收一次,在处理它的时候,会先把该类型的信号block,进程可以忽略信号,也可以捕捉这个信号,执行信号处理程序。
2.当内核从一个异常处理程序返回的时候,准备吧控制传递给某个进程p的时候,会检查进程p违背阻塞的待处理信号集合。如果这个集合不为空,那么内核选择集合中的某个信号k(越小越好,因为linux里面编号越小,优先级越高),并且进入k的处理程序。
1 | /* 改变blocked向量的值,若oldset!=null,会用来保存以前blocked向量的值 */ |
singal函数
1 | sighandler_t signal(int signum,sighandler_t handler) |
如果handler不是SIG_IGN或者SIG_DFL,handler 就是用户定义的函数的地址,这个函数叫做信号处理程序。这个过程叫设置信号处理程序,调用过程叫做捕获信号,执行信号处理过程叫做处理信号
阻塞和解除阻塞信号
隐式阻塞:例如信号s对应程序S,当在处理程序S的时候,如果发送进程一个信号s,那么直到S返回,s会一直是待处理而不被接收。
显式阻塞:使用sigprocmask函数和其辅助函数,明确阻塞和解除阻塞选定的信号。
临时阻塞一个信号
1 |
|
非本地跳转
一些原则:
1.注意保存与恢复errno
2.当访问一个全局数据结构的时候,阻塞所有的信号
3.用volatile声明全局变量,告诉编译器不要缓存这个变量,那么每次引用g的时候,都要从内存中读取g的数值。