当前位置: 首页 > news >正文

国外优秀ui设计网站商丘网站建设运营公司

国外优秀ui设计网站,商丘网站建设运营公司,一链一网一平台,如何鉴别网站有没有做301重定向文章目录 一、进程创建1.fork函数2.fork函数返回值3.写时拷贝4.fork常规用法5.fork调用失败的原因 二、进程终止1.进程退出码2.进程退出场景3.进程常见退出方法 三、进程等待1.为什么要进行进程等待2.如何进行进程等待1.wait方法2.waitpid方法3.获取子进程status4.进程的阻塞等… 文章目录 一、进程创建1.fork函数2.fork函数返回值3.写时拷贝4.fork常规用法5.fork调用失败的原因 二、进程终止1.进程退出码2.进程退出场景3.进程常见退出方法 三、进程等待1.为什么要进行进程等待2.如何进行进程等待1.wait方法2.waitpid方法3.获取子进程status4.进程的阻塞等待方式与非阻塞等待方式5.进程等待总结 四、进程程序替换1.创建子进程的目的2.什么是进程程序替换3.进程程序替换的原理4.如何进行进程程序替换(1) 替换函数(2) 函数命名理解(3)函数的使用 五、实现一个简易的shell1.shell的初步实现2.什么是当前路径3.内建命令/外部命令4.shell 完整代码 一、进程创建 1.fork函数 fork函数是Linux中的一个非常重要的系统调用函数它用于在当前进程下创建一个新的进程新进程是当前进程的子进程我们可以使用man 2号手册来查看fork函数 // 头文件 #include unistd.h// 创建一个子进程 pid_t fork(void);//返回值子进程中返回0父进程返回子进程id出错返回-1进程调用fork当控制转移到内核中的fork代码后内核做以下任务 分配新的内存块和内核数据结构给子进程将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表当中fork返回开始调度器调度 当一个进程调用fork之后就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以 开始它们自己的旅程看如下程序 #include stdio.h #include unistd.h #include stdlib.h #include sys/types.hint main() {pid_t id fork();if(id -1){printf(fork fail\n);exit(-1);}else if(id 0){// childwhile(1){printf(我是子进程pid:%d,ppid:%d,id:%d\n,getpid(),getppid(),id);sleep(1);}}else{// parentwhile(1){printf(我是父进程pid:%d,ppid:%d,id:%d\n,getpid(),getppid(),id);sleep(1);}}return 0; }结论fork之前父进程独立执行fork之后父子两个执行流分别执行。注意fork之后谁先执行完全由调度器 决定。 一个小技巧我们在编写Makefile的时候目标文件的依赖方法中可以使用 来表示形成的目标文件即依赖关系中 : 左边的内容用 来表示形成的目标文件即依赖关系中:左边的内容用 来表示形成的目标文件即依赖关系中:左边的内容用^“表示目标文件的依赖文件即依赖关系中”:右边的内容 2.fork函数返回值 fork函数有两个返回值子进程返回0父进程返回的是子进程的pid 我们学过C/C之后知道一个函数的返回值最多只有一个那么我们如何理解fork函数有两个返回值呢 我们知道fork函数是一个系统调用即fork函数是操作系统为我们提供的一个操作接口所以fork函数也是由操作系统实现的所以当我们调用fork函数时其实是操作系统帮我们创建子进程。一个函数在正常的执行的情况下函数return之前函数的主体功能肯定已经被执行完了对于fork函数来说fork函数的作用是创建子进程所以fork函数在return之前就已经创建了子进程那么此时就存在两个进程。既然存在两个进程那么fork函数也就会被返回两次因为每一个进程都会return所以fork函数有两个返回值 我们如何理解fork返回之后给父进程返回子进程的pid给子进程返回0呢 因为一个父进程可能有多个子进程而一个子进程只能有一个父进程父进程需要子进程的pid来判别不同的子进程而子进程则不需要判别父进程直接调用getppid即可获得父进程的pid 如何理解同一个id值怎么可能会保存不同的id值让if 和 else if 同时执行 我们知道子进程会拷贝父进程的PCB,数据结构以及页表但是当一个进程对其数据进程写入的时候就会发生写时拷贝改变页表的映射关系在一个新的空间存储数据fork函数返回而返回的本质就是写入所以谁先返回就谁先写入因为进程具有独立性发生写时拷贝所以可以if 和else if同时执行 3.写时拷贝 我们看下面的程序 #include stdio.h #include unistd.hint global_val 100;int main() {pid_t id fork();if(id 0){printf(fork error\n);return 1;}else if(id 0){int cnt 0;while(1){printf(我是子进程,pid:%d,ppid:%d | global_val:%d,global:%p\n, getpid(),getppid(),global_val,global_val);sleep(1);cnt;if(cnt 10){printf(子进程已经更改了全局的变量啦.....\n);global_val 300;}}}else{while(1){printf(我是父进程,pid:%d,ppid:%d | global_val:%d,global:%p\n, getpid(),getppid(),global_val,global_val);sleep(2);}}return 0; }我们发现子进程和父进程中global_val变量的地址相同但是值却不相同我们知道操作系统会每一个进程都创建一个进程地址空间和页表然后通过页表将地址空间映射到物理内存 对于父进程来说父进程和子进程共享代码和数据但是为了保证进程的独立性当其中一个进程需要修改数据的时候就会发生写时拷贝–操作系统会在物理内存重新开辟一块空间然后将原空间中的数据拷贝到新的空间然后在修改映射关系最后再让进程修改对应的数据 所以表面省父子进程的global_val的 地址相同但是这只是虚拟地址相同而物理地址并不相同所以父子进程的global_val的值并不相同对于接收fork函数返回值的变量id来说也是如此先进行return的进程会对id进行写时拷贝所以对于父子进程来说id的值也不相同 4.fork常规用法 fork函数一般用于下面两种场景 1.一个父进程希望复制自己使父子进程同时执行不同的代码段。例如父进程等待客户端请求生成子进程来处理请求。 2.一个进程要执行一个不同的程序。例如子进程从fork返回后调用exec函数 5.fork调用失败的原因 有如下两种原因可能会导致fork函数调用失败 1.系统中有太多的进程 2.实际用户的进程数超过了限制 我们可以写一个死循环创建进程的程序来测试我们当前的操作系统能够创建多少个进程 #include stdio.h #include unistd.hint main() {int cnt 0;while(1){int ret fork();if(ret 0){printf(fork error!, cnt: %d\n, cnt);break;}else if(ret 0){//childwhile(1) sleep(1);}//partentcnt;}return 0; }上面的代码可能会导致服务器或者虚拟机直接挂掉所以我这里就不进行测试了有兴趣的小伙伴可以进行测试一下。 二、进程终止 1.进程退出码 我们运行一个进程是为了让进程为我们完成某一项任务既然是为了完成一个任务那么我们就可能会关心该进程完成任务的情况所以就需要对任务的执行结果进行判定此时就需要用到进程退出码进程退出码的作用就是标定一个进程的执行结果是否正确不同的进程退出码表示不同的执行结果一般来说进程退出有三种情况 1.进程退出(代码跑完),结果正确此时return 0 2.进程退出(代码跑完)结果不正确此时 return !0 3.代码没跑完程序异常了此时退出码无意义 对于 !0 来说不同的数字又对应着不同的错误码我们可以使用系统提供的退出码的映射关系也可以自己设定不同的退出码所对应的错误信息我们可以使用C语言提供的strerror函数打印出系统提供的错误码的映射关系 #include stdio.h #include string.hint main() {int i 0;for(i 0; i 100; i){printf(%d:%s\n,i,strerror(i));}return 0; }在Linux中存在一个变量 ? -该变量中始终保存着最近一个进程执行完成时的退出码我们可以使用 echo $?来查看最近一个进程的退出码 我们可以看到我们再次输入 echo #?指令的时候打印的值为0这是由于 echo本身也是一个可执行程序我们使用 echo查看 ? 时 echo也会被运行所以我们后面再次查看 $? 时得到的结果为0 2.进程退出场景 进程退出时一个有三种情景 1.代码运行完毕且结果正确–此时退出码为0 2.代码运行完毕但是结果错误-此时退出码为非0 3.代码异常终止–此时退出码无意义 3.进程常见退出方法 进程退出有以下三种方法 1.main函数return返回 2.调用exit终止程序 3.调用_exit终止程序 我们平时最常用的就是通过main函数return返回退出程序但是其实我们也可以通过库函数exit和系统调用_exit直接终止程序 库函数exit 头文件:stdlib.h 函数原型:void exit(int status);status:status 定义了进程的终止状态父进程通过wait来获取该值函数功能:终止程序我们可以看到exit会将我们的进程直接终止无论程序代码是否执行完毕 系统调用 _exit 头文件unistd.h函数原型void _exit(int status);status:status定义了进程的终止状态父进程通过wait来获取该值函数功能终止进程【注意】 参数status 定义了进程的终止状态父进程通过wait来获取该值 说明虽然status是int但是仅有低8位可以被父进程所用。所以_exit(-1)时在终端执行$?发现返回值255 exit 和 -exit 的区别 exit 终止进程会主动刷新缓冲区_exit终止进程不会刷新缓冲区 我们以下面的例子来进行说明 #include stdio.h #include unistd.h #include stdlib.hint main() {printf(process is running);exit(1);// _exit(1);printf(process is running done\n);return 0; }我们可以看到exit 终止进程会主动刷新缓冲区_exit终止进程不会刷新缓冲区分析如下 1.由于exit是C语言库函数而_exit是系统调用所以可以肯定的是exit的底层是_exit函数,exit是_exit的封装 2.由于计算机体系结构的限制CPU之间和内存交互所以数据会先被写入到缓存区待缓存区刷新时才被打印到显示器上而上面的程序中我们没有使用\n进行缓冲区的刷新可以看到exit最后打印了process id running,而_exit什么也没有打印所以exit在终止程序后会刷新缓冲区而_exit终止程序后不会刷新缓冲区 3.由于exit的底层是_exit而_exit并不会刷新缓冲区可以反映出缓冲区不在操作系统内部而是在用户空间 进程退出不仅有正常的退出还有不正常的退出比如Ctrl C终止进程或者程序中除0野指针空指针的解引用等问题程序就会异常退出 exit最后也会调用exit, 但在调用exit之前还做了其他工作 1.执行用户通过 atexit或on_exit定义的清理函数。 2.关闭所有打开的流所有的缓存数据均被写入 3.调用_exit 三、进程等待 1.为什么要进行进程等待 为什么要进行进程等待呢有如下原因 我们创建一个进程的目的是为了让其帮我们完成某种任务而既然是完成任务进程在结束前就应该返回任务执行的结果供父进程或者操作系统进行读取所以一个进程在退出的时候不能立即释放其全部的资源–对于进程的代码和数据操作系统可以释放因为该进程已经不会再被执行了但是该进程的PCB应该被保留下来因为PCB中存放着该进程的各种状态的代码其中就包括退出状态代码。对于父子进程来说当子进程退出后如果父进程不对子进程的退出状态进行读取进程一旦变成僵尸状态那就刀枪不入“杀人不眨眼”的kill -9 也无能为力因为谁也没有办法杀死一个已经死去的进程。从而就会造成内存的泄漏 所以我们需要父进程通过进程等待的方式回收子进程资源获取子进程退出信息并让操作系统回收子进程的资源(释放子进程的PCB) 进程等待的本质 我们知道子进程的退出信息是存放在子进程的task_struct中的所以进程等待的本质就是从子进程task_struct中读取退出信息然后保存到对应的变量中取 2.如何进行进程等待 1.wait方法 我们可以通过wait系统调用来进行进程等待 头文件: sys/types.h sys/wait.h函数原型:pid_t wait(int* status)status:输出型参数获取子进程退出状态返回值:成功返回被等待进程的pid,失败返回-1我们以以下的例子来说明wait的使用 #include stdio.h #include unistd.h #include string.h #include stdlib.h #include sys/types.h #include sys/wait.hint main() {pid_t id fork();if(id 0){//子进程int cnt 5;while(cnt){printf(我是子进程: %d, 父进程: %d, cnt: %d\n, getpid(), getppid(), cnt--);sleep(1);}exit(0); //进程退出}sleep(15);int status 0;pid_t ret wait(status);if(ret 0){printf(wait success: %d, sig number: %d, child exit code: %d\n, ret, (status 0x7F), (status8)0xFF);}sleep(5);return 0; }我们可以使用一个监控脚本来检测子进程从创建到终止到被父进程回收过程中状态的变化 while :; do ps axj | head -1 ps axj | grep mytest | grep -v grep; sleep 1; done我们可以看到最开始父子进程都处于睡眠状态之后子进程运行5s之后此时由于父进程还要休眠10s所以没有对子进程进行回收所以子进程变成僵尸进程10s过后父进程使用wait系统调用对子进程进行进程等待所以子进程由僵尸状态变成了死亡状态 2.waitpid方法 我们也可以使用waitpid来进行进程等待 头文件:sys/types.h sys/wait.h函数原型:pid_t waitpid(pid_t pid,int* status,int option);pid :pid1,等待任意一个子进程与wait等效pid 0,等待其进程与pid相等的子进程status输出型参数获取子进程退出状态不关心则可以直接设置为NULLoption等待方式option 0 - 阻塞等待option WNOHANG - 非阻塞等返回值:waitpid调用成功时返回被等待进程的pid;如果设置了WNOHANG,且waitpid发现没有已退出的子进程可收集则返回0调用失败则返回-1我们以以下的例子来说明waitpid的使用 #include stdio.h #include unistd.h #include string.h #include stdlib.h #include sys/types.h #include sys/wait.hint main() {pid_t id fork();if(id 0){//子进程int cnt 5;while(cnt){printf(我是子进程: %d, 父进程: %d, cnt: %d\n, getpid(), getppid(), cnt--);sleep(1);}exit(12); //进程退出}// 父进程sleep(10);int status 0; // 不是被整体使用的有自己的位图结构pid_t ret waitpid(id, status, 0);if(ret 0){printf(wait success: %d, sig number: %d, child exit code: %d\n, ret, (status 0x7F), (status8)0xFF);}sleep(5);return 0; }我们可以看到waitpid和wait还是有很大区别waitpid可以传递id来指定等待特定的子进程也可以指定option来指明等待方式 【总结】 pid_ t waitpid(pid_t pid, int *status, int options); 返回值 当正常返回的时候waitpid返回收集到的子进程的进程ID 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在 参数 pid Pid-1,等待任一个子进程。与wait等效。 Pid0.等待其进程ID与pid相等的子进程。 status: WIFEXITED(status): 若为正常终止子进程返回的状态则为真。查看进程是否是正常退出 WEXITSTATUS(status): 若WIFEXITED非零提取子进程退出码。查看进程的退出码 options: WNOHANG: 若pid指定的子进程没有结束则waitpid()函数返回0不予以等待。若正常结束则返回该子进程的ID。 如果子进程已经退出调用wait/waitpid时wait/waitpid会立即返回并且释放资源获得子进程退出信息。 如果在任意时刻调用wait/waitpid子进程存在且正常运行则进程可能阻塞。 如果不存在该子进程则立即出错返回 3.获取子进程status 我们在上面的程序中打印sig number和child exit code的时候打印的格式分别为status 0x7F), (status8)0xFF);这是由于status的位图结构决定我们这是使用的 我们知道wait和waitpid都有一个参数该参数是一个输出型参数由操作系统填充如果传递的参数是 NULL则表示不关心子进程的退出状态的信息否则操作系统会根据该参数将子进程的退出信息反馈给父进程 status 不能简单的当作整形来看待可以当作位图来看待具体细节如下图其中我们只需要研究status的低16比特位 我们可以看到status低两个字节的内容被分成了两个部分–第一个字节的前七位表示退出信号最后一位表示core dump标志第二个字节表示退出的状态退出状态即表示进程退出时的退出码 对于正常退出的程序来说退出信号和core dump都标志为0退出状态等于退出码对于异常终止的程序来说退出信号为不同终止原因对应的数字此时退出状态就没有意义 所以status正确的读取方式如下 printf(exit signal:%d,exit code:%d\n,(status 0x7f),(status8 0xff));其中status按位与上0x7f表示保留低七位其余九位全部置为0从而得到退出信号 status右移8位得到退出状态再按位与上0xff是为了得到防止右移时高位补1的情况 WIFEXITED与WEXITSTATUS宏 Linux提供了WIFEXITED与WEXITSTATUS宏来帮助我们获取status中的退出状态和退出信号而不再需要我们自己执行按位操作 WIFEXITED(status): 若为正常终止子进程返回的状态则为真。查看进程是否是正常退出 WEXITSTATUS(status): 若WIFEXITED非零提取子进程退出码。查看进程的退出码 4.进程的阻塞等待方式与非阻塞等待方式 waitpid函数的第三个参数用于指定父进程的等到方式 其中option代表阻塞等待方式option为WNOHANG代表非阻塞等待 阻塞式等待即当父进程执行到waitpid函数时如果子进程还没有退出那么父进程就只能阻塞在waitpid函数直到子进程退出父进程通过waitpid读取退出信息后才能执行后面的代码 而非阻塞式等待则是当父进程执行到waitpid函数时父进程会直接读取子进程的状态并返回然后接着执行后面的代码不会等待子进程退出 轮询 轮询是指父进程在非阻塞的状态的前提下以循环的方式不断对子进程进行进程等待只带子进程退出 #include stdio.h #include stdlib.h #include unistd.h #include string.h #include sys/types.h #include sys/wait.h #include assert.h#define NUM 10typedef void (*func_t)(); //函数指针func_t handlerTask[NUM];//样例任务 void task1() {printf(handler task1\n); } void task2() {printf(handler task2\n); } void task3() {printf(handler task3\n); }void loadTask() {memset(handlerTask, 0, sizeof(handlerTask));handlerTask[0] task1;handlerTask[1] task2;handlerTask[2] task3; }int main() {pid_t id fork();// fork返回-1 直接断言断死assert(id ! -1);if(id 0){//childint cnt 10;while(cnt){printf(child running, pid: %d, ppid: %d, cnt: %d\n, getpid(), getppid(), cnt--);sleep(1);}exit(10);}loadTask();// parentint status 0;while(1){pid_t ret waitpid(id, status, WNOHANG); //WNOHANG: 非阻塞- 子进程没有退出, 父进程检测时候立即返回if(ret 0){// waitpid调用成功 子进程没退出//子进程没有退出我的waitpid没有等待失败仅仅是监测到了子进程没退出.printf(wait done, but child is running...., parent running other things\n);for(int i 0; handlerTask[i] ! NULL; i){handlerTask[i](); //采用回调的方式执行我们想让父进程在空闲的时候做的事情}}else if(ret 0){// 1.waitpid调用成功 子进程退出了printf(wait success, exit code: %d, sig: %d\n, (status8)0xFF, status 0x7F);break;}else{// waitpid调用失败printf(waitpid call failed\n);break;}sleep(1);}return 0; }5.进程等待总结 1.为了读取子进程的退出结果以及回收子进程的资源我们需要进程等待 2.进程等待的本质是父进程从子进程的task_struct 中读取退出信息然后保存到status中 3.我们可以通过wait和waitpid系统调用获取退出信息完成进程等待 4.status参数是一个输出型参数父进程通过wait/waitpid函数将子进程的退出信息写入到status中 5.status以位图的方式存储包括退出状态和退出信号如果退出信号不为0那么退出状态就没有意义 6.我们可以使用系统提供的宏 WIFEXITED和WEXITSTATUS来分别获取status中的退出状态和退出信号 7.进程等待方式分为阻塞式等待方式和非阻塞式等待方式阻塞式等待方式用0来标识非阻塞式等待方式用宏WONHANG来进行标识 8.由于非阻塞式等待不会等待子进程退出所以我们需要使用轮询的方式来不断的获取子进程的退出信息 四、进程程序替换 1.创建子进程的目的 创建子进程由两个目的 1.想让子进程执行父进程代码的一部分执行父进程对应的磁盘代码中的一部分 2.想让子进程执行一个全新的程序让子进程想办法加载磁盘上指定的程序执行新程序的代码和数据 2.什么是进程程序替换 对于创建子进程的第二个目的–让子进程来执行一个不同的程序就是程序替换 进程程序替换是指父进程使用fork函数来创建子进程后子进程通过调用exec系列的函数来执行另一个程序当进程调用某一种exec函数时该进程的用户空间代码和数据完全被新程序替换然后执行新的程序 但是原进程的task_struct和mm_struct以及进程的id后不会改变页表的映射关系可能会发生改变所以调用exec系列函数时并不会创建一个新的进程而是让原来的进程去执行另外一个新程序的代码和数据 3.进程程序替换的原理 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。所以进程程序替换就是用新的程序的代码和数据区替换原进程物理内存中的代码和数据。 4.如何进行进程程序替换 (1) 替换函数 Linux提供了一系列的exec函数来实现进程程序替换其中包括六个库函数和一个系统调用 我们可以看到实现进程程序替换的系统调用函数就只有一个–execve其他的一系列的exec函数都是为了满足不同的替换场景而对execve系统调用进行的封装其底层还是调用execve 六个库函数如下 #include unistd.hextern 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 execvpe(const char *file, char *const argv[],char *const envp[]);这些函数一旦调用成功就代表着原程序的代码和数据已经被新的程序替换了也就是说原来的程序的后续的语句就都不会再被执行了所以exec函数调用成功之后没有返回值因为该返回值接收已经没有意义了。只有exec函数调用失败原程序可以继续往下执行时exec返回才有意义 【总结】这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1所以exec函数只有出错的返回值而没有成功的返回值 (2) 函数命名理解 这些函数原型看起来很容易混,但只要掌握了规律就很好记 l(list) : 表示参数采用列表 v(vector) : 参数用数组 p(path) : 有p自动搜索环境变量PATH路径下搜索文件即对于替换Linux指令相关程序时不需要我们带路径 e(env) : 表示自己维护环境变量 (3)函数的使用 我们想要执行一个程序首先需要找到该可执行程序二是指定程序执行的方式即按照怎样的方式去执行对于exec函数来说p’和非’p’用来找到程序l’和’v’用来指定程序执行方式e’用来指定环境变量 execl execlp exec函数的使用其实很简单第一个参数为我们需要替换的程序的路径如果该程序在PATH环境中且exec函数带有p我们可以不带理解只写函数名 我们以linux指令ls为例我们知道ls是Linux中usr/bin目录下的一个可执行程序且该程序处于PATH环境变量中那么如果我们需要替换该程序exec函数的第一个参数如下 execl(/usr/bin/ls,...); // 需要带路径 execlp(ls,..); // 可以不带路径我们需要注意的是带p的exec函数可以不带路径的前提是被替换程序处于PATH环境变量中如果没有处于PATH环境变量中那么我们 仍然需要带路径 第二个参数是我们如何执行我们的程序这里我们需要记住一点在Linux命令行中给程序如何执行我们就如何传递参数。需要注意的是命令行中多个指令是以空格为分割的一整个字符串而exec函数中我们需要对不同的选项进行分割即每一个选项都要单独分为一个字符串所以可以看到exec函数中存在可变参数列表…同时我们现在需要将最后一个可变参数设置为NULL表示参数传递完毕 // 命令行中怎么传递就怎么传递 ls -a -l execl(/usr/bin/ls,ls,-a,l,NULL); execlp(ls,ls,-a,l,NULL);我们需要注意的是Linux中ls 其实是使用alise命令设置别名的所以我们执行ls的时候默认带了-colorauto选项它让不同类型的文件带有不同的颜色 所以我们在ls进程程序替换时如果我们想要让不同类型的文件表现为不同的颜色的话那么我们需要显式的传递-colorauto选项 execl(/usr/bin/ls,ls,-a,-l,--colorauto,NULL); execlp(ls,ls,-a,-l,--colorauto,NULL);下面我们以一个具体的例子来演示如何进行进程程序替换 #include stdio.h #include stdlib.h #include unistd.h #include sys/types.h #include sys/wait.hint main() {pid_t id fork();if(id -1){perror(fork);exit(1);}else if(id 0){printf(pid:%d,child process running..\n,getpid());int ret execl(/usr/bin/ls,ls,-a,-l,--colorauto,NULL);if(ret -1){printf(process exec fail\n);exit(1);}printf(pid:%d,child process done..\n,getpid());return 0;}int status;pid_t ret waitpid(id, status, 0);if(ret -1){perror(waitpid);return 1;}else{printf(wait success: exit code: %d, sig: %d\n, (status8)0xFF, status 0x7F);}return 0; }我们可以看到我们在命令行当使用ls -a -l和我们使用进程程序替换得到的结果是一样的 execv execvp exec函数中v代表参数使用数组的形式进行传递–argv是一个指针数组数组里面的每一个元素都是指针每一个指针都指向一个参数(字符串),同样最后一个元素指向NULL代表参数传递完毕 我们还是以ls指令为例来进行演示 char* argv[]{(char*)ls,(char*)-a,(char*)-l,(char*)--colorauto,NULL }; execlv(/usr/bin/ls,argv); execvp(ls,argv); 由于ls,-a等字符串都是常量字符串而argv里面的参数是char* const而不是const char* 的所以我们这里需要强转一下不强转问题也不大 execle execvpe exec函数中的e代表环境变量-和argv一样envp也是一个指针数组数组里面的每一个元素都是一个指针指向一个环境变量(字符串),我们可以显式的初始化envp来传递我们自定义的环境变量但是这也代表了我们放弃了字体默认的环境变量 char *const envp_[] {(char*)MYENV11112222233334444,NULL}; execle(.mybin,./mybin,NULL,envp);mybin.c #include stdio.h #include stdlib.hint main() {// 系统就有printf(PATH:%s\n, getenv(PATH));printf(PWD:%s\n, getenv(PWD));// 自定义printf(MYENV:%s\n, getenv(MYENV));printf(我是另一个C程序\n);printf(我是另一个C程序\n);printf(我是另一个C程序\n);printf(我是另一个C程序\n);printf(我是另一个C程序\n);printf(我是另一个C程序\n);printf(我是另一个C程序\n);printf(我是另一个C程序\n);printf(我是另一个C程序\n);return 0; }myexec.c #include stdio.h #include unistd.h #include stdlib.h #include assert.h #include sys/types.h #include sys/wait.hint main(int argc, char *argv[]) {printf(process is running...\n);pid_t id fork();assert(id ! -1);if(id 0){execle(./mybin, mybin, NULL, envp_); //自定义环境变量exit(1); //must failed}int status 0;pid_t ret waitpid(id, status, 0);if(ret0) printf(wait success: exit code: %d, sig: %d\n, (status8)0xFF, status 0x7F); }我们可以看到这里我们只获取到了自定义的环境变量MYENV而系统的环境变量PATH和PWD则是没有被获取到 我们可以通过传递environ来获取系统的环境变量 if(id 0) {extern char** environ;execle(./mybin, mybin, NULL, environ); //自定义环境变量exit(1); //must failed }但是这个时候我们又不能够获取我们自定义的环境变量那么我们该如何同时获取到自定义环境变量和系统环境变量呢这个时候我们可以使用putenv函数将自定义环境变量导入到系统环境变量中然后通过传递环境变量environ来实现 putenv((char*)MYENV4443332211); //将指定环境变量导入到系统中 environ指向的环境变量表 execle(./mybin, mybin, NULL, environ); //实际上默认环境变量你不传子进程也能获取五、实现一个简易的shell 1.shell的初步实现 我们实现一个简易的命名行解释器大概需要分为一下几个步骤 1.输出提示符即我们平时写指令的左边的提示符 2.从终端获取命令进行指令输入 3.解析输入的命令 4.创建子进程 5.进程程序替换 6.进程等待 #include stdio.h #include stdlib.h #include unistd.h #include sys/types.h #include sys/wait.h #include string.h#define NUM 1024 // 一个指令的最大长度 #define OPT_NUM 64 // 一个指令的最多选项char lineCommand[NUM]; // 保存输入命令的数组 char* myargv[OPT_NUM]; // 保存选项的数组int main() {while(1){// 输出提示符printf([用户名主机名 当前路径]$);fflush(stdout);// 从键盘(stdin)获取指令输入char* s fgets(lineCommand,sizeof(lineCommand)-1,stdin);//最后一个位置来保存极端场景下的\0if(s NULL){perror(fgets);exit(1);}lineCommand[strlen(lineCommand)-1] \0, //消除命令行中最后的换行符// 将输入的字符串解析为说个字符串存放到myargv数组中即字符串切割myargv[0] strtok(lineCommand, );int i 1;while(myargv[i] strtok(NULL, ));// 创建子进程pid_t id fork();if(id -1){perror(fork);exit(1);}else if(id 0){// 子进程进行进程程序替换execvp(myargv[0],myargv);exit(1);}else{int status 0;pid_t ret waitpid(id, status,0);if(ret -1){perror(waitpid);exit(1);} }}return 0; }这样我们就完成了Linux中的一些基本指令了但是我们发现我们使用ls的时候没有颜色的功能我们可以在程序中对ls指令进行单独的判断然后手动的为其加上–colorauto选项 if(myargv[0] ! NULL strcmp(myargv[0],ls) 0) {myargv[1] (char*)--colorauto; }2.什么是当前路径 我们在运行我们上面的程序的时候会发现一个问题当我们使用cd更换路径的时候再使用pwd命令还是显示我们原来的路径 我们在解决这个问题之前我们需要先理解什么是当前路径 我们可以看到在test程序运行起来之后在系统中一共有两个路径一个exe路径是指test可执行程序在磁盘中的路径而cwd(current working directory) 为当前进程的工作目录就我们平常所说的当前路径 在Linux中我们可以使用chdir这个系统调用来改变进程的工作目录 在理解什么是当前进程的工作目录之后我们就可以解释为什么我们的shell执行cd命令后目录不会改变了 myshell是通过创建子进程的方式去执行命令行中的各种指令的也就是说cd命令是由子进程去执行的那么自然被改变的也是子进程的工作目录父进程的工作目录则不会改变 而我们使用pwd指令来查看当前路径时cd指令对应的子进程已经执行完毕退出了此时myshell又会给pwd创建一个新的进程且这个子进程的工作目录和父进程的工作目录相同所以pwd打印出来的工作目录不会改变 我们想要解决这个问题就需要使用chdir将父进程的工作目录修改为指定的目录即可所以这里我们呀需要对指令进行单独的判断 // cd 改变父进程的路径 if(myargv[0] ! NULL strcmp(myargv[0],cd) 0) {if(myargv[1] ! NULL){chdir(myargv[1]);// myargv[1]中保存着指定路径}continue; // 下面的语句不需要在继续执行了以为你cd的目的已经达到了 }3.内建命令/外部命令 Linux中的命令一共分为两种–内建命令和外部命令 内建命令是shell程序的一部分其功能在bash源代码中不需要派生子进程来执行也不需要借助外部程序文件来运行而是由shell进程本身内部的逻辑来完成外部命令则是通过创建子进程然后进行进程程序替换运用外币程序文件等方式来完成 我们可以使用type命令来区分Linux中的内置命令为外部命令 我们对cd指令就是以内置命令的方式来进行处理的–myshell遇到cd命令时由自己直接改变工作目录处理完毕直接continue而不会通过创建子进程的方式来完成不过对于pwd我们没有单独处理成内建命令 同时我们发现echo也是一个内建命令这也解释了为什么echo$ 变量可以查看本地变量和echo$?可以获取最近的一个进程的退出码原因如下 本地变量只是在当前进程有效但是使用echo查看本地变量时shell并不会创建子进程而是直接在当前进程中查找所以可以查看本地变量 shell可以通过进程等待的方式获取一个子进程的退出状态然后将其保存在?变量中当命令行输入echo$?时直接输出?中的内容然后将?置为0(echo正常退出的退出码)也不需要创建子进程 所以我们也可以在我们的shell程序加入echo命令了 int lastCode 0; // 保存退出码 int lastSig 0; //保存退出信号 if(myargv[0] ! NULL myargv[1] ! NULL strcmp(myargv[0], echo) 0) {if(strcmp(myargv[1], $?) 0){printf(%d, %d\n, lastCode, lastSig);}else{printf(%s\n, myargv[1]);}continue; }// fork之后添加的内容 lastCode ((status 8) 0xff); lastSig (status 0x7f);4.shell 完整代码 #include stdio.h #include stdlib.h #include unistd.h #include sys/types.h #include sys/wait.h #include string.h#define NUM 1024 // 一个指令的最大长度 #define OPT_NUM 64 // 一个指令的最多选项char lineCommand[NUM]; // 保存输入命令的数组 char* myargv[OPT_NUM]; // 保存选项的数组int lastCode 0; int lastSig 0;int main() {while(1){// 输出提示符printf([用户名主机名 当前路径]$);fflush(stdout);// 从键盘(stdin)获取指令输入 输入\nchar* s fgets(lineCommand,sizeof(lineCommand)-1,stdin);//最后一个位置来保存极端场景下的\0if(s NULL){perror(fgets);exit(1);}//消除命令行中最后的换行符lineCommand[strlen(lineCommand)-1] \0, // 将输入的字符串解析为说个字符串存放到myargv数组中即字符串切割// ls -a -l -i - ls -a -l -i - 1-nmyargv[0] strtok(lineCommand, );int i 1;if(myargv[0] ! NULL strcmp(myargv[0],ls) 0){myargv[1] (char*)--colorauto;}// 如果没有子串了strtok-NULL, myargv[end] NULLwhile(myargv[i] strtok(NULL, ));// cd 改变父进程的路径// 如果是cd命令不需要创建子进程,让shell自己执行对应的命令本质就是执行系统接口// 像这种不需要让我们的子进程来执行而是让shell自己执行的命令 --- 内建/内置命令if(myargv[0] ! NULL strcmp(myargv[0],cd) 0){if(myargv[1] ! NULL){chdir(myargv[1]);// myargv[1]中保存着指定路径}continue; // 下面的语句不需要在继续执行了因为你cd的目的已经达到了}if(myargv[0] ! NULL myargv[1] ! NULL strcmp(myargv[0], echo) 0){if(strcmp(myargv[1], $?) 0){printf(%d, %d\n, lastCode, lastSig);}else{printf(%s\n, myargv[1]);}continue;}// 创建子进程pid_t id fork();if(id -1){perror(fork);exit(1);}else if(id 0){// 子进程进行进程程序替换execvp(myargv[0],myargv);exit(1);}else{int status 0;pid_t ret waitpid(id, status,0);if(ret -1){perror(waitpid);exit(1);} lastCode ((status 8) 0xff);lastSig (status 0x7f);}}return 0; }
http://www.dnsts.com.cn/news/58753.html

相关文章:

  • 十大高端网站定制设计师网站免费高清素材软件
  • icann官方网站广州企业网站设计公司
  • 什么牛网站建设廊坊网络公司网站
  • 网站建设策划基本流程建站网络公司
  • 网站内容注意事项溜冰鞋 东莞网站建设
  • 网站设置为信任站点网络规划设计师教程 下载
  • 国外刺绣图案设计网站开发一个手游需要多少钱
  • 家里做网站买什么服务器好营销公司排行
  • 广州建设网站平台企业微信crm
  • 做哪个网站比较有流量公司管理系统网站模板下载
  • 网站界面优化网页建站价格
  • 网站开发费用一般为多少钱西安做网站费用
  • 企业年金网上查询入口对网站提出的优化建议
  • 网站注册备案做外贸 网站
  • 网站制作加教程视频教程网站开发职责
  • 有哪些做任务网站做网站推广的销售电话开场白
  • 横向网站模板优化型网站是什么意思
  • 网站动态图片如何做网站建设发展历程ppt
  • 微商城网站建设平台合同wordpress主题制作难吗
  • 中国做进出口的网站wordpress
  • 怎么做免费的网站链接企业网站制作免费下载
  • 1.2婚庆网站建设的目的厚街网站建设费用
  • 做美食网站的图片大全网站建设行业前景
  • 网站建设的基本要素新网站如何被快速收录
  • 宿迁房产交易中心官网seo排名工具站长
  • 义乌市场官方网站上海口碑最好的家装
  • 如何购买网站域名网站上做的图片不清晰是怎么回事
  • 专业网站建设最权威seo案例分析
  • 阿里云做的网站程序员厦门市网站建设软件开发公司
  • 汇川区住房和城乡建设厅网站WordPress grace7主题