-
fork() 函数的使用
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值; 在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
#include <stdio.h> #include <unistd.h> int main() { printf("before pid=%d\n", getpid()); pid_t pid = fork(); int a = 100; if (pid < 0) { return -1; } else if (pid == 0) { a = 20; printf("this is child! pid=%d a=%d\n", getpid(), a); } else { sleep(1); printf("this is father a=%d pid=%d\n", a, getpid()); } return 0; }
-
僵尸进程
产生原因: 子进程先于父进程退出, 部分资源没回收。当进程退出并且父进程没有读取到子进程退出的返回代码是就会产生僵尸进程,僵尸进程会以终止状态保存在进程表中,并且会一直等待父进程读取退出状态代码
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if (pid < 0) { return -1; } else if (pid == 0) { printf("i am child! pid=%d\n", getpid()); sleep(3); exit(1); } else { printf("i am parent! pid=%d\n", getpid()); while(1) { sleep(1); printf("i don't care my child\n"); } } }
可以看到 zombie子进程在三秒后变为僵尸状态
- 危害
- 维护退出状态本身就要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护。
- 如果一个父进程创建了很多子进程,都不会回收,就会造成内存资源浪费。因为数据结构对象本身就要占内存。
- 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
- 危害
-
孤儿进程
产生原因: 父进程先于子进程退出
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if (pid < 0) { return -1; } else if (pid == 0) { printf("i am parent pid=%d\n", getpid()); sleep(10); } else { printf("i am child pid=%d\n", getpid()); sleep(3); exit(1); } return 0; }
-
环境变量
-
概念
指在操作系统中用来指定操作系统运行环境的一些参数。环境变量通常具有特殊用途,在系统中具有全局特性。
环境变量通常具有全局属性,可以被子进程继承下去。 -
常见的环境变量
(1)PATH:指定命令的搜索路径
(2)HOME:指定用户的主工作目录(即用户登录到Linux系统中时,默认的目录)
(3)HISTSIZE:指保存历史命令记录的条数
(4)SHELL:当前Shell,它的值通常是/bin/bash -
查看环境变量的方法
echo $NAME //NAME:环境变量名称 -
和环境变量相关的命令
(1)echo:显示某个环境变量值
(2)export:设置一个新的环境变量
(3)env:显示所有环境变量
(4)unset:清楚环境变量
(5)set:显示本地定义的shell变量和环境变量 -
setenv
作为setenv函数
作用:增加或者修改环境变量。
注意:通过此函数并不能添加或修改 shell 进程的环境变量,或者说通过setenv函数设置的环境变量只在本进程,而且是本次执行中有效。如果在某一次运行程序时执行了setenv函数,进程终止后再次运行该程序,上次的设置是无效的,上次设置的环境变量是不能读到的。
头文件:#include<stdlib.h>
注:stdlib.h在Linux和Windows中略不同,比如setenv函数是用在linux中的,在Windows中没有setenv函数而用putenv来代替
函数声明:int setenv(const char *name,const char * value,int overwrite);
函数说明:setenv()用来改变或增加环境变量的内容。参数name为环境变量名称字符串。参数 value则为变量内容,参数overwrite用来决定是否要改变已存在的环境变量。如果没有此环境变量则无论overwrite为何值均添加此环境变量。若环境变量存在,当overwrite不为0时,原内容会被改为参数value所指的变量内容;当overwrite为0时,则参数value会被忽略。返回值 执行成功则返回0,有错误发生时返回-1。
相关函数:getenv,putenv,unsetenv作为Linux中setenv命令
Linux中的功能:查询或显示环境变量
语法:setenv [变量名称] [变量值]setenv用于在C shell设置环境变量的值
用法:setenv ENVVAR value
ENVVAR 为所要设置的环境变量的名。value为所要设置的环境变量的值
例:setenv PATH "/bin:/usr/bin:usr/sbin:"设置环境path的搜索路径为/bin,/usr/bin以及/usr/sbin -
export
Linux中的功能:设置或显示环境变量(比如我们要用一个命令,但这个命令的执行文件不在当前目录,这样我们每次用的时候必须制定执行文件的目录,麻烦,在代码中先执行export,这个相当于告诉程序,执行某某东西时,需要的文件或什么东西在这些目录里)
说明:在shell中执行程序时,shell会提供一组环境变量。export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅及于该次登陆操作。
语法:export [-fnp] [变量名称] = [变量设置值]
参数说明:-f 代表[变量名称]中为函数名称。
-n 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。
-p 列出所有的shell赋予程序的环境变量。
延伸:export设置环境变量是暂时的,只在本次登录中有效,可修改如下文件来使命令长久有效。
注意:
1、执行脚本时是在一个子shell环境运行的,脚本执行完后该子shell自动退出;
2、一个shell中的系统环境变量才会被复制到子shell中(用export定义的变量);
3、一个shell中的系统环境变量只对该shell或者它的子shell有效,该shell结束时变量消失(并不能返回到父shell中)。
4、不用export定义的变量只对该shell有效,对子shell也是无效的。一个变量创建时,它不会自动的为在它之后创建的shell进程所知。而命令export可以向后面的shell传递变量的值。当一个shell脚本调用并执行时,它不会自动得到原来脚本(调用者)里定义的变量的访问权,除非这些变量已经被显示地设置为可用。export命令可以用于传递一个或多个变量的值到任何后续脚本。
export设置环境变量是暂时的,只在本次登录中有效,若想要使得开机时自动加载这个环境变量免除以后每次设置,可将其写入/etc/re.local
-
-
进程退出
-
进程退出场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
-
进程常见退出方法
-
正常终止
可以通过echo $?查看进程退出码
过程:
1. 从main返回 2. 调用exit() 3. _exit()
-
异常退出
ctrl + c 信号终止
-
_exit函数
#include <unistd.h> void _exit(int status); // 参数: status 定义了进程的终止状态,父进程通过wait来获取该值 // status 只有低八位可以被父进程所用 所以 exit(-1)时, 在终端查看退出码为255
-
exit函数
#include <unistd.h> void exit(int status); // exit最后也会调用exit,但在调用exit之前,还做了其他工作 // 1. 执行用户通过atexit或on_exit定义的清理函数 // 2. 关闭所有打开的流,所有的缓存数据均被写入 // 3. 调用_exit
-
return退出
return是一种常见的退出进程的方法。执行return n等同于执行exit(n), 因为调用main的运行时函数会将main的返回值当做exit的参数
-
-
-
进程等待
子进程先于父进程退出,父进程由于处理子进程退出的通知,就会造成僵尸进程, 进而造成资源泄露,所以父进程通过进程等待的方式,回收子进程退出后的资源,获取子进程退出信息
-
进程等待的函数
-
wait函数
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int* status); // 返回值: 成功返回被等待进程的pid,失败返回-1 // 参数: 输出型参数, 获取子进程的退出状态,不关心则可以设为NULL
-
waitpid函数
pid_t waitpid(pid_t pid, int* status, int options); // 返回值:当正常返回的时候waitpid返回收集到的子进程的进程id // 如果设置了选项WNOHANG, 调用中waitpid发现没有已退出的子进程可收集,则返回0 // 如果调用中出错,则返回-1, 这时errno被设置成相应的值以指示错误所在; // 参数: // pid: // pid = -1 等待任意一个子进程, 与wait等效 // pid > 0 等待期id与pid相等的子进程 // status: // WIFEXITED(status) 若为正常终止子进程返回的状态,则为真(查看进程是否正常退出 WEXITSTATUS(status) 查看进程的退出码 // options: //WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若 子进程的id
-
注意
- 如果子进程已经退出,调用wait/waitpid时, wait/waitpid 会立即返回,并且释放资源,获得子进程的退出信息
- 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞
- 如果不存在子进程,则立即出错返回。
-
获取子进程status
传递NULL, 表示不关心子进程的退出状态信息
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程
status不能简单的当做整型来看:如下图
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if (pid < 0){ return -1; } if (pid == 0) { sleep(5); exit(10); } else { int status; int ret = wait(&status); if (ret > 0 && (status & 0x7f) == 0 ) { printf("child exit code: %d\n", (status >> 8)&0xff); } else if(ret > 0) { printf("sig code: %d\n", status&0x7f); } } }
-
-
进程的阻塞等待方式
#include <unistd.h> #include <stdio.h> #include <sys/wait.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if (pid < 0) { return -1; } else if (pid == 0) { printf("child is runing, pid is: %d\n", getpid()); sleep(5); exit(100); } else { int status = 0; pid_t ret = waitpid(-1, &status, 0); printf("wait\n"); if (WIFEXITED(status) && ret == pid) { printf("wait child 5s success, child return code is %d\n", WEXITSTATUS(status)); } else { printf("wait error!"); return 1; } } return 0; }
-
进程等待的非阻塞方式
#include <unistd.h> #include <stdio.h> #include <sys/wait.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if (pid < 0) { return -1; } else if (pid == 0) { printf("child is run start, pid is: %d\n", getpid()); sleep(5); exit(1); } else { int status = 0; pid_t ret = 0; while(ret == 0) { ret = waitpid(-1, &status, WNOHANG); if (ret == 0) { printf("child is running\n"); } sleep(1); } if (WIFEXITED(status) && ret == pid) { printf("wait child 5s success, child return code is %d\n", WEXITSTATUS(status)); } else { printf("wait error!"); return 1; } } return 0; }
-
实现miniShell
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { while(1) { printf("[minShell]$ "); fflush(stdout); char buf[1024] = {0}; if (scanf("%[^\n]%*c", buf) != 1) { getchar(); } char *argv[32]; int argc = 0; char *ptr = buf; while(*ptr != '\0') { if (!isspace(*ptr)) { argv[argc++] = ptr; while(!isspace(*ptr) && *ptr != '\0') { ptr++; } }else { *ptr = '\0'; ptr++; } } argv[argc] = NULL; int pid = fork(); if (pid < 0) { exit(-1); }else if (pid == 0) { execvp(argv[0], argv); exit(0); } wait(NULL); } return 0; }
-
-
process_create
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> typedef void (*FUNC)(void* arg); int process_create(pid_t* pid, void* func, void* arg) { pid_t pids; pids = fork(); if (pids < 0) { return -1; } else if (pids == 0) { *pid = pids; ((FUNC)func)(arg); } else { int status = 0; pid_t ret = 0; while(ret == 0) { ret = waitpid(-1, &status, WNOHANG); } if (!WIFEXITED(status) && ret != pids) { return -1; } } return 0; } void testFunc(int a){ printf("the value is %d\n", a); } int main() { pid_t pid = 0; int ret = process_create(&pid, (void*)testFunc, (void*)2); printf("create process id:%d ret=%d\n", pid, ret); return 0; }
Linux进程控制
最新推荐文章于 2023-12-18 17:45:00 发布