Linux 进程控制
来源:互联网 发布:java api 1.6英文版 编辑:程序博客网 时间:2024/06/16 03:18
进程创建
在操作系统中 一旦开始运行一个可执行程序 该可执行程序在磁盘中的数据和代码被加载到进程中 且同时进程中出现了用于描述其性质的唯一与其对应的PCB 则该可执行程序的实例 它的进程 被创建出来了。
进程地址空间
了解进程地址空间是 学习进程控制的理论基础。
地址空间绝不是物理内存 它只是一个抽象概念。
关于地址空间的结构在我的另一篇博客中提到了:
http://blog.csdn.net/x__016meliorem/article/details/78741868
操作系统为每个进程提供一个假象——–好像他们每个进程在独占地址空间。
一台n位的机器上 地址空间是2的n次方个可能地址的集合 这个地址空间每个进程拥有一份。
linux的PCB(task_struct)中有一个指向 mm_struct结构体的指针
mm_struct结构体是用来描述进程地址空间的实体
这里是关于mm_struct的详细介绍
http://blog.csdn.net/qq_26768741/article/details/54375524
子进程:
根据任务的需要 创建子进程必不可少。
创建子进程的目的:
1. 一个应用程序有时不可避免要让用户感觉同时完成 同类或不同任务任务
2. init 1号进程是操作系统刚刚启动时由内核 引导执行的 他是所有linux进程的父进程 其他进程其实都是它创建出来的子进程。
创建一个子进程在linux下有两个系统调用
1. fork()
#include<unistd.h>pit fork(void)
在子进程中返回 0 父进程中返回 子进程id 错误返回 -1;
fork()函数干了什么?
在一个进程的代码中 一旦调用了fork() 遂即创建了一个以自己为父进程的子进程。
fork()出的子进程是父进程的副本 但是拥有自己独立的虚拟地址空间
子进程从父进程那里继承了 数据和代码 堆、栈空间(主要是mm_struct的副本) 代码段父子进程共享、
数据理论上是相同的两份 实际上为了节省空间采用写时拷贝机制。
——–父子进程如果都没修改原数据段的数据 则父子进程实际上共用数据 如果有一方修改了数据段数据 则需要将原父进程数据段数据拷贝一份给子进程。
子进程继承父进程什么?
用户号UIDs和用户组号GIDs
环境变量
堆栈
共享内存
打开文件的描述符
执行时关闭(Close-on-exec)标志
信号(Signal)控制设定
进程组号
当前工作目录
根目录
文件方式创建屏蔽字
资源限制
控制终端
子进程独有什么?
进程号PID
不同的父进程号
自己的文件描述符和目录流的拷贝
子进程不继承父进程的进程正文(text),数据和其他锁定内存(memory locks)
不继承异步输入和输出
父进程和。。。
进程拥有独立的地址空间和PID参数。
fork()将创建成功的子进程添加到进程列表中 返回后 父子两个执行流从fork()之后的代码分流 谁先执行 由调度器决定 不缺定。
fork()之前只有父进程一个单独的执行流。
———-这个代码干了什么?
int main(){ pid_t pid1 = fork(); pid_t pid2 = fork(); printf("hello world"); exit(0);}
嵌套的fork()程序 程序调用了两次fork 程序运行了四个进程
每个进程调用的printf()可以任意顺序执行。
2. vfork()
- vfork() 与fork()不同的是 vfork()创建出的子进程在父进程的地址空间内运行
- vfork()保证子进程先运行 在子进程调用exec()或exit()后 调度器才可能让父进程运行。
#include<unistd.h>#include<stdio.h>#include<stdlib.h>int g_val = 0;int main(){ pid_t id = vfork(); if(id == 0 ){ sleep(3); g_val = 100; printf("I am Child pid:%d, ppid:%d,g_val:%d,&g_val:%p \n",\ getpid(),getppid(),g_val,&g_val); exit(1); }else if(id > 0){ sleep(2); printf("I am Father pod:%d, ppid:%d, g_avl:%d, &g_val:%p \n",\ getpid(), getppid(),g_val,&g_val); }else{ exit(2); } return 0;}
程序第九行的fork()函数被改成了vfork()函数
1. 子进程睡眠 3秒 父进程睡眠 2 秒 如果这里是fork() 必然父进程先执行 可输出结果确是子进程先知执行 exit(1)后 父进程才执行。
- 这里输出的结果 父子进程的g_val都是 子进程修改后的值 100 而不是初始化的
20证明了子进程在父进程的地址空间内运行。
这个特点和线程类似 线程就是在进程的地址空间内运行
进程终止
- 进程退出的场景
- 代码运行完毕 ,结果正确
- 代码运行完毕, 结果错误
- 代码没有运行完毕 异常终止 (可能是收到了信号)
ctrl + c 其实就是向一个前台进程 发送一个终止进程的信号。
_exit 函数
#include<unistd.h>void _exit(int status);
参数status定义了 进程的推出状态 父进程通过wait函数来获取该值 int status的最低八位表示子进程退出时收到的信号
次低八位表示正常退出时的推出码。
&? 命令可以看到上一个执行结束进程的退出码。
exit函数
#include<unistd.h>void exit(int status);
exit函数最后也会调用 _exit函数 调用前了干三件事
1. 调用atexit() on_exit() 定义的数理函数。
2. 关闭所有流 刷新所有缓冲区。
#include <stdio.h>#include<unistd.h>#include<stdlib.h>void Func(){ printf("Func Runing!\n"); //exit(1);}void Funcc(){ printf("Funcc Done\n");}int main(){ Func(); atexit(Funcc);//call user self-define clean Func and than exit process printf("Func Done!\n"); printf("main Done!"); // _exit(0); exit(0); //return 0;}
上述代码调用_exit() 与exit() 的区别 这里有证明:
return
main() 函数的返回值 return n; 实际上是exit()函数中的退出码。
进程等待
进程等待的必要性
父进程对其创建出的子进程有义务回收其退出信息
如果子进程退出 父进程不回收子进程退出状态 父进程自己又不退出就会造成“僵尸进程问题” 导致内存泄漏。(子进程退出时 关闭所有自己的文件描述符 释放在用户空间分配的内存 但他的PCB还保留着 里面存放它的推出信息 需要父进程接收 则是PCB才会被释放 )
一个进程一旦成为僵尸进程 连kill -9 信号也无法杀死 。
父进程派给子进程的任务 父进程有必要必须知道它的完成情况。
父进程通过进程等待方式 回收子进程退出信息 系统回收资源进程等待的方法
wait函数
#include <sys/types.h>#include <sys/wait.h>pid_t wait(int *status);
等待成功返回被等待进程的pid 失败返回
参数为输出型参数 函数调用完成后存储子进程的退出状态
如果父进程不关心子进程推出状态 参数传NULL;
#include<unistd.h>#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>int g_val = 0;int main(){ pid_t id = fork(); if(id == 0 ){ g_val = 100; while(1){ printf("I am Child pid:%d, ppid:%d,g_val:%d,&g_val:%p \n",\ getpid(),getppid(),g_val,&g_val); sleep(3); exit(123); } //exit(1); }else if(id > 0){ //sleep(2); printf("I am Father pod:%d, ppid:%d, g_avl:%d, &g_val:%p \n",\ getpid(), getppid(),g_val,&g_val); }else{ perror("vfork"); } int status = 0; pid_t ret = wait(&status); if(ret > 0){ printf("wait suzzess ! ret : %d status :%d\n", ret, status); } return 0;}
这个小程序子进程也调用了wait 但是因为它没有子进程 所以出错返回-1
只有父进程调用的wait 返回子进程的pid (大于一) 所以有打打印
这里可以看出来 父进程执行完毕后 没有直接退出 而是等待子进程exit后拿到子进程推出信息status 释放了子进程的PCB (status后面介绍)
这里需要说明一点是 wait函数等待子进程退出是阻塞式等待
只要子进程没有退出 父进程什么也不干 就只等待。父进程代码停在wait函数里面
waitpid函数
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options)`
waitpid 函数的返回值
1. 如果等待成功 返回被等待的子进程id
2. waitpid 可以选择非阻塞方式等待(轮询) 即 多次查看子进程是否有子进程推出 有的话回收其推出信息 没有的话 父进程先运行其他代码 。 一段时间后再检查是否有子进程退出。 第三个参数options如果设置为 WHOHANG 表示为轮询等待 如果这一次等待时没有子进程退出 则 返回0. options参数设置为0表示阻塞等待。
3. 如果调用失败返回 -1 errno自动被设置为相应的值指示错误所在
参数 pid == -1 表示等待任意一个进程 参数 pid > 0表示要等待的参数的pid
status参数和wait函数的status参数一样
status 的最低八位表示 如果进程接到信号而被杀死的话 接收到的信号是什么 。
status的次第八位表示进程 正常退出时的退出码。
阻塞式waitpid 程序正常退出
#include<unistd.h>#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>int g_val = 0;int main(){ pid_t id = fork(); if(id == 0 ){ g_val = 100; while(1){ printf("I am Child pid:%d, ppid:%d,g_val:%d,&g_val:%p \n",\ getpid(),getppid(),g_val,&g_val); sleep(3); exit(123); } //exit(1); }else if(id > 0){ //sleep(2); printf("I am Father pod:%d, ppid:%d, g_avl:%d, &g_val:%p \n",\ getpid(), getppid(),g_val,&g_val); }else{ perror("vfork"); } int status = 0; pid_t ret = waitpid(id,&status,0); if(ret > 0){ printf("ret:%d, status :%d, exit code: %d, sig: %d\n",ret, status, (status >> 8)&0xff, (status)&0xff); } return 0;}
轮询waitpid 用信号杀死进程
介绍四个宏
WIFEXITED(status) //返回子进程是否是正常退出的
WIFSIGNALED(status)//返回子进程是否是收到信号退出的
WEXITSTATUS(status)//返回子进程正常退出退出状态
WTERMSIG(status)//返回子进程收到的信号
(这里所讲的正常退出指的是代码跑完了 并不关心结果是否正确)
#include<unistd.h>#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>int g_val = 0;int main(){ pid_t id = fork(); if(id == 0 ){ g_val = 100; while(1){ printf("I am Child pid:%d, ppid:%d,g_val:%d,&g_val:%p \n",\ getpid(),getppid(),g_val,&g_val); sleep(3); //exit(123); } exit(1); }else if(id > 0){ //sleep(2); printf("I am Father pod:%d, ppid:%d, g_avl:%d, &g_val:%p \n",\ getpid(), getppid(),g_val,&g_val); }else{ perror("vfork"); } int status = 0; do{ pid_t ret = waitpid(id,&status,WNOHANG); if(ret == 0){ printf("Father doing other things!\n"); sleep(1); }else if(ret > 0){ if(WIFEXITED(status)){//正常退出 printf("child running done exit code : %d \n",WEXITSTATUS(status)); }else if(WIFSIGNALED(status)){ printf("child is quit, but it is killed SIG :%d", WTERMSIG(status)); } break; }else{ printf("wait failed!\n"); break; } }while(1); return 0;}
进程程序替换
进程程序替换是个重点 应用场景十分广泛
原理图:
fork()创建子进程 运行的是从父进程地址空间拷贝的代码 和父进程一样 运行时可能运行不同的代码分支
很多时候需要 子进程完成一项全新的和父进程无关联的任务
这时调用 exec系列函数 将进程的数据和代码全部替换为 新程序的数据和代码 但是它不创建新的PCB 原进程id也不变。
shell脚本输入命令实际上就是 fork()子进程之后再exec
exec系列函数
#include <unistd.h> extern 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[]); #include <unistd.h> int execve(const char *filename, char *const argv[], char *const envp[]);
解释 :如此多exec系列函数如何使用?
有 p 的要输入替换的程序的名字 系统自己程序路径
没有p的 要输入程序的路径
有 l 的 后几个参数都要输入 原本在命令行输入的 命令和参数 作为一个一个的参数传递 最后一定用用NULL表示结束。
有v的 将原在命令行上的命令和参数以数组形式传入
exec系列函数代码 将子进程代码和数据替换成了 ls -a -i -l
父进程创建子进程后 只需要等待子进程即可
exec函数只有失败返回值 -1 没有成功返回值 成功后进程代码和数据都被替换了
#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>#include<unistd.h>int main(){ pid_t id = fork(); if(id == 0){ printf("execl running \n"); //sleep(1); //execl("/bin/ls","ls","-a","-l","-n",NULL); //execlp("ls","ls","-a","-l","-n",NULL); char* _argv[] = { "ls", "-a","-l","-n",NULL }; execl("/bin/ls","ls","-a","-i","-n",NULL); //execv("/bin/ls",_argv); //execvp("ls",_argv); //execlp("ls","ls","-l","-i",NULL); exit(1); }else{ pid_t ret = waitpid(id,NULL,0); if(ret > 0){ printf("waitpid success\n"); } } return 0;}
issolve/70/gravity/SouthEast)
exec vp execlp 函数可以向替换的程序传递环境变量
[ym@localhost process]$ cat mybin.c#include<stdio.h>#include<stdlib.h>int main(){ printf("I am mybin: %s\n",getenv("MYENV")); return ;}
这里有一个mybin.c文件打印 “MYENV”环境变量 这个环境变量并不存在 所以改代码运行结果为null。
用execle函数将mybin程序的代码和数据 替换到子进程中 并传给他还境变量 输出结果就不同了
#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/wait.h>#include<unistd.h>int main(){ pid_t id = fork(); if(id == 0){ printf("execl running \n"); char* _env[] = {"MYENV=/FORTEST/" , NULL}; execle("./mybin","mybin",NULL,_env); exit(1); }else{ pid_t ret = waitpid(id,NULL,0); if(ret > 0){ printf("waitpid success\n"); } }` return 0;}
几个exec函数之间的关系
函数与进程的相似性:
函数有自己的栈帧 进程有自己的地址空间
函数有参数返回值 进程有exec中的参数 exit()中的返回值
函数有call return 进程有frok exec exit
通过参数和返回值这在拥有私有数据函数之间的调度 也运用到了进程之间 linux鼓励这种模式扩展到进程之间。
- linux进程及进程控制
- linux进程及进程控制
- linux进程及进程控制
- linux进程及进程控制
- LINUX进程控制
- Linux 进程控制
- linux进程控制
- linux进程控制
- Linux的进程控制
- linux进程控制
- LINUX进程控制 笔记
- linux进程控制
- linux 进程控制说明
- linux 进程控制入门
- Linux进程控制
- Linux进程控制
- LInux进程控制
- Linux进程控制
- 最近发现了个页面生成二维码的js工具
- 在iOS11下app图标变空白的问题解决
- pid好文转载
- 连接数据库地址后面加上?autoReconnect=true&useUnicode=true&characterEncoding=utf8 日志
- OkHttpUtils的使用
- Linux 进程控制
- 40张技术图谱,架构师阶梯 (附高清下载)
- 这7个不可错过的数据可视化技术,能让地图惊喜跃动
- 阿里机器人都会批改作文了!深圳无人驾驶公交车试运行;360 开源深度学习平台 XLearning;
- set()赋值 get()得到该属性
- MySql导出表结构(语句导出,navicat工具即可)
- MySQ基础入门系列之——字符与日期数据处理
- Mac下nginx安装和配置
- Ehcache 学习笔记