main进程终止:
图片来自linux环境高级编程150页
1 #include<cstdlib> 2 #include<cstdio> 3 4 static void my_exit1(void) 5 { 6 printf("first exit handler "); 7 } 8 static void my_exit2(void) 9 { 10 printf("second exit handler "); 11 } 12 13 int main() 14 { 15 if(atexit(my_exit1) != 0) //#include<cstdlib>先注册的终止处理程序后被调用,FILO 16 printf("error_exit1 "); 17 if(atexit(my_exit2) != 0) 18 printf("error_exit2 "); 19 printf("main is done "); 20 exit(0); // == return 0; 0表示终止状态码,正常终止 #include<cstdlib> 21 }
上面代码片段使用不同的链接方式:
获取环境变量(以前的main含有第三个参数:环境变量。最后虽然取消了第三个参数,但是环境变量还是会在程序执行之前传入)
1 const char* env ; 2 env = getenv("PATH"); //#include<cstdlib> 3 printf("PATH = %s",env);
fork()系统调用:
先来张图片:这不就是shell的核心程序吗!在操作系统完成启动后(中间过程登录等略去)会弹出一个shell,shell的主体结构如下,从命令行获取cmd,然后fork()一个进程就行执行,原本的shell程序执行体阻塞。
这里面有三个问题,①为什么是!fork()?②为什么使用exec()或者说exec()干嘛了?③wait()做什么事情。
①fork()是一个系统调用,那么必然触发int0x80中断进入内核态,进入内核态后,fork创建一个子进程(创建进程无非就是分配资源),fork主要创建子进程的pcb以及栈,因为fork创建的子进程和父进程共享代码段,数据段等等,只有子进程进行写入时才会触发写时拷贝机制进行内存的重新分配,写入为子进程分配的内存空间中。有意思的地方到了,fork()完成子进程的创建后,现在已经不仅仅只有父进程一个执行序列了,而是两个执行序列,还有一个是子进程的执行序列,上图if()语句被两个执行序列都执行了(因为子进程共享父进程的代码段),但是区别来了,父进程调用fork返回后的返回值是不等于0的,因此,if条件不会满足,就会跳过if后面的语句去执行wait(),而新创建的子进程执行序列,执行到if语句时,if条件成立,因此会执行exec系统调用。
②exec()是个系统调用,因此也要通过int0x80中断进入内核态,调用sys_exec,它的主要作用就是,将cmd程序的入口代码的cs,ip放到内核栈中,当从内核返回时,会进行内核栈的弹栈,就返回到了cmd程序的执行入口。因此exec系统调用成功使fork()出来的子进程去执行一个可执行程序(这不就是shell中输入的那么指令么,例如ls,exec将ls的执行代码的入口地址写入了子进程对应的内核栈中,弹栈时会到ls入口处执行)。
③wait():父进程调用wait()后处于一个阻塞状态,父进程开始阻塞,cpu便执行进程调度,子进程成功的被调度后执行。这样就很完美。当然wait还有另个一个作用,不仅仅是阻塞,它阻塞到这里会等待子进程结束,子进程结束后,父进程通过wait()会获得子进程的状态,释放子进程剩余的资源。如果不使用wait,也就是说子进程结束后,父进程不去获取子进程的结束状态,那么该子进程就会成为了僵尸进程。
④上诉三个过程加上最上面的main程序启动终止图就更容易理解了。这里的重点便是fork()后实际产生了两个指令序列,以及exec将子进程重父进程共享过来的指令序列进行修改成exec的过程,wait做后续子进程的处理。
类unix系统的登录
本地登录:系统完成启动后,会产生一个init进程(用户态pid为1的进程,所有进程都是由init直接或间接派生出来的)。init进程读取/etc/ttys,为每一个终端设备调用一次fork,生成的子进程调用exec函数执行getty程序,getty调用open函数打开终端,终端被打开则文件描述符被0、1、2被创建与终端绑定,终端便可读可写。然后子进程继续调用exec将login代码载入执行,屏幕便有了login:。用户即可以登录了。
图片来自unix环境高级编程214页
网络登录:如果服务器上运行一个telnet远程服务程序,当客户端有远程连接请求到达时,telnet打开一个伪终端(pseudo terminal)并fork一个子进程,将文件描述符0、1、2与伪终端绑定,执行一个exec函数加载login代码,然后通过网络获得客户端输入的用户名和密码,进行验证,验证通过,则获得shell。
图片来自unix环境高级编程217页
登录成功后其实做了很多事情,如图:①工作目录改为用户的家目录,②改变(伪)终端的所有权,③设置进程组ID(重要),④用户环境变量,调用shell等等。