【Linux 应用编程】进程管理 - 进程、线程和程序
基本概念
程序和进程的区别
程序是平台相关的二进制文件,只占用磁盘空间。编写完程序代码后,编译为可执行的二进制文件即可。
进程是运行中的程序,占用 CPU、内存等系统资源。
通过 Shell 命令,可以在终端启动进程,例如执行 ls
命令:
- 找到命令对应的二进制文件
- 使用 fork() 函数创建新的进程
- 在新创建的进程中调用 exec 函数组,加载命令对应的二进制文件,并从 main 函数开始执行
并发和并行
并发 concurrent:在一个时间段内,处理的请求总数。个数越多,并发越大。
并行 parallel:任意时刻能够同时处理的请求数。通常跟 CPU 内核数量相关。
Linux 进程的状态
Linux 的进程有以下 6 种状态:
- D:深度睡眠状态,不可中断,处于这种状态的进程不能响应异步信号
- R:进程处于运行态或就绪状态,只有在该状态的进程才可能在 CPU 上运行
- S:可中断的睡眠状态,处于这个状态的进程因为等待某种事件的发生而被挂起
- T:暂停状态或跟踪状态
- X:退出状态,进程即将被销毁
- Z:退出状态,进程成为僵尸进程
命令行控制进程
ps 命令
通过 ps aux
可以查看当前机器上的进程,其中 STAT 列的第一个字符就是进程的状态:
# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 190764 2140 ? Ss 2018 12:35 /usr/lib/systemd/systemd --system --deserialize 20
root 2 0.0 0.0 0 0 ? S 2018 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? S 2018 1:24 [ksoftirqd/0]
kill 命令
kill 命令用于结束进程,语法如下:
kill [-s signal|-p] [-q sigval] [-a] [--] pid...
kill -l [signal]
执行 kill 命令,系统会发送一个 SIGTERM 信号给对应的进程,请求进程正常关闭。SIGTERM 是有可能会被阻塞的。kill -9
命令用于强制杀死进程,系统给对应程序发送的信号是 SIGKILL,即 exit。exit 信号不会被系统阻塞。
示例:
kill -9 11235
PID
每个进程在创建的时候,内核都会为之分配一个全局唯一的进程号。
getpid 和 getppid 函数
通过 getpid 函数可以获取当前进程的 PID。getppid 函数可以获取父进程的 PID。
通过 ps aux
可以查看进程的 PID,资源消耗情况,通过 ps -ef
可以查看当前进程及其父进程的 PID。通过 pstree
命令可以以树状关系查看所有进程。
函数原型:
#include <sys/types.h>
#include <unistd.h>pid_t getpid(void);
pid_t getppid(void);
环境变量
除了通过 main 函数的第三个参数获取环境变量,还可以通过 environ 全局变量或 getenv() 函数来获取。
getenv 函数原型:
#include <stdlib.h>char *getenv(const char *name);
char *secure_getenv(const char *name);
环境变量示例
#include <stdio.h>
#include <stdlib.h>extern char** environ;int main(int argc, char* argv[], char* env[])
{int i = 0;char* myenv = NULL;while(env[i]){printf("env[%d] is: %s\n", i, env[i++]);}i = 0;while(environ[i])puts(environ[i++]);myenv = getenv("PATH");puts(myenv);return 0;
}
孤儿进程和僵尸进程
PCB(Process Control Block,进程控制块)是每个进程都有的数据结构,用于保存进程运行所需的信息,例如文件描述符表。
wait() 函数用来帮助父进程获取其子进程的退出状态。当进程退出时,内核为每一个进
程保存了退出状态信息。如果父进程未调用 wait() 函数,则子进程的退出信息将一直保存在内存中。
孤儿进程
Linux 中,每个进程在退出的时候,可以释放用户区空间,但是无法释放进程本身的 PCB 所占用的内存资源。PCB 必须由父进程释放。
父进程在创建子进程后退出,子进程变成孤儿进程。为防止内存泄漏,孤儿进程被 init 进程领养,init 进程变成孤儿进程的父进程。
下面示例中,父进程先退出:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>int main()
{pid_t pid = fork();if (pid == 0) {printf("child pid is: %d, ppid is: %d\n", getpid(), getppid());sleep(1);printf("child pid is: %d, ppid is: %d\n", getpid(), getppid());}else if (pid > 0){ sleep(0.5);printf("parent pid is: %d, ppid is: %d\n", getpid(), getppid());printf("parent exit\n");}return 0;
}
输出:
parent pid is: 3348, ppid is: 713
parent exit
child pid is: 3349, ppid is: 1
child pid is: 3349, ppid is: 1
僵尸进程
子进程退出了,父进程一直未调用 wait 或 waitpid 函数,子进程就变成了僵尸进程。
代码控制进程
进程入口函数 main
可执行的二进制文件,都是从 main 函数开始执行的。main 函数有 3 种原型定义:
int main();
int main(int argc, char *argv[]);
int main(int argc, char *argv[], char *env[]);
参数:
- argc:参数个数
- argv:参数数组,每个参数都是字符串
- env:环境变量数组,每个环境变量都是字符串
进程基本操作
注意,Shell 终端无法检测进程是否创建了子进程。在进程执行完毕后,Shell 会立即回到交互状态,此时如果子进程还在输出数据,会打印在 Shell 的命令提示符之后。可以在父进程中 sleep 一下。
创建进程
Linux 中用 fork() 函数创建新进程,函数原型如下:
#include<unistd.h>pid_t fork(void);
返回值:
成功创建进程时,会对父子进程各返回一次,对父进程返回子进程的 PID,对子进程返回 0。通过条件分支语句可以分别进行不同处理。失败则返回小于 1 的错误码。
fork 函数执行的时候,会将当前正在运行的进程完整的复制一份,提供给子进程。子进程从 fork 函数之后开始执行。
创建多个子进程
通过 for 循环,可以创建多个子进程,只需要在每次 fork 之后判断如果是子进程则结束循环即可。通过循环下标可以判断当前子进程是第几次创建的:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{int i = 0;int number = 5;pid_t pid;for (i = 0; i < number; i++){pid = fork();if (pid == 0) break;}// 子进程if (i == 0) printf("first process, pid = %d\n", getpid());if (i == 1) printf("second process, pid = %d\n", getpid());//...// 父进程if (i == number) printf("parent process, pid = %d\n", getpid());return 0;
}
终止进程
进程的终止分为两种:
- 正常终止:
- 从 main() 函数中 return 返回,实际上也是调用的 exit() 函数
- 调用 exit() 类函数
- 异常终止:
- 调用 abort() 函数
- 接收到终止信号
exit 函数原型如下:
#include <stdlib.h>void exit(int status);
exit(0)
等价于 return 0
。
exec 函数组
fork 函数可以复制一份父进程,得到的子进程跟父进程有完全一样的代码跟数据。之后两个进程各自执行,互不影响。
实际上我们通常需要子进程执行不同的代码,这时就需要通过 exec 函数加载代码段,并跳转到新代码段的 main 入口执行。
函数原型:
#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[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
exec 函数组是在 exec 上加 l、v、p、e 四个后缀形成的,这些函数作用相同,只是在参数列表上存在差别。
- 后缀 p:用文件名做参数,如果文件名中不含路径则会去 PATH 环境变量所指定的各个目录中搜索可执行文件。无后缀 p 则使用绝对路径名来指定可执行文件的位置。
- 后缀 e:表示可以传递一个指向环境字符串指针数组的指针,环境数组需要以 NULL 结束。而无此后缀的函数则使用调用进程中 environ 变量为新程序复制现有的环境。
- 后缀 l:用 list 形式来传递新程序的参数,传给新程序的所有参数以可变参数的形式传递,最后一个参数必须是 NULL。
- 后缀 v:用 vector 形式来传递新程序的参数,传给新程序的所有参数放入一个字
符串数组中,数组以 NULL 结束。
返回值:exec 族函数报错时才有返回值 -1,否则无返回值。如果执行到后面的代码,就是出错了。
示例:
char* myenv[] = {"TEST=666", "HOME=/home/kika", NULL};
execle("home/kika/test", "hello", "world", myenv);
完整示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{int pid = fork();if (pid > 0) {exit(0);} else if (pid == 0) {execle("home/kika/test", "hello", "world", myenv);perror("execle error");exit(-1);} else {perror("fork error");}return -1;
}
wait 和 waitpid 函数
通常,在父进程中调用 wait 函数,可以查看子进程的退出信息,让子进程撤单结束。wait 函数是阻塞式的,waitpid 可以设置为非阻塞的。父进程根据创建的子进程个数,在循环中通过 wait 函数逐个回收子进程。而 waitpid 函数则可以通过 PID 等待指定的子进程退出。
wait 函数调用一次只会回收一个子进程。多个子进程需要调用多次 wait。
函数原型:
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait 参数:
- status:进程退出的原因。可以通过预定义的宏来检查:
- WIFEXITED(status):若为正常终止子进程返回状态,则为真
- WEXITSTATUS(status):获取子进程传给 exit 或 return 的参数
- WIFSIGNALED(status):若为异常终止子进程返回状态,则为真
- WTERMSIG(status):获取使子进程退出的信号
waitpid 参数:
- pid:用于设置 waitpid 工作方式。
-1
:等价于 wait,等待任意子进程退出0
:等待组 ID 等于调用进程的组 ID 的任一子进程退出> 0
:等待 PID 等于该数值的进程退出< -1
:等待其组 ID 等于该数值的任一子进程退出
- options:用于设置 waitpid 是否阻塞执行。0 则阻塞,设为 WNOHANG 则非阻塞。
返回值:成功时返回退出子进程的 PID,没有子进程时返回 -1.
综合示例
创建三个子进程,分别运行自定义程序,shell 程序,未定义程序(段错误)。然后在父进程中通过 wait 回收所有子进程,并分别判断退出原因:
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>int main(int argc, char* argv[])
{int num = 3;int i;pid_t pid;for (i = 0; i < 3; i++) {pid = fork();if (pid == 0) break;}if (i == 0) {execlp("ps", "ps", "aux", NULL);perror("execlp ps error");exit(1);} else if (i == 1) {execl("/root/test/api/process/myls", "", NULL);perror("execl myls error");exit(1);} else if (i == 2) {execl("./error", "", NULL);perror("execl ./error");exit(1);} else {int status;pid_t pid;while (pid = wait(&status) != -1) {printf("children PID is: %d\n", pid);if (WIFEXITED(status)) {printf("return value is: %d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("died by signal: %d\n", WTERMSIG(status));}}}return 0;
}
转载于:https://www.cnblogs.com/kika/p/10851501.html
【Linux 应用编程】进程管理 - 进程、线程和程序相关推荐
- 【Linux 内核】进程管理 ( 内核线程概念 | 内核线程、普通进程、用户线程 | 内核线程与普通进程区别 | 内核线程主要用途 | 内核线程创建函数 kernel_thread 源码 )
文章目录 一.内核线程概念 二.内核线程.普通进程.用户线程 三.内核线程.普通进程区别 四.内核线程主要用途 五.内核线程创建函数 kernel_thread 源码 一.内核线程概念 直接 由 Li ...
- Linux系统编程 74 孤儿进程和僵尸进程
Linux系统编程 74 孤儿进程和僵尸进程 学习笔记 孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 init进程,称为init进程领养了孤儿进程. init进程会去接替 ...
- 【Linux系统编程】特殊进程之守护进程
00. 目录 文章目录 00. 目录 01. 守护进程概述 02. 守护进程查看方法 03. 编写守护进程的步骤 04. 守护进程代码 05. 附录 01. 守护进程概述 守护进程(Daemon Pr ...
- 【Linux系统编程】特殊进程之孤儿进程
00. 目录 文章目录 00. 目录 01. 孤儿进程概述 02. 孤儿进程代码 03. 附录 01. 孤儿进程概述 父进程运行结束,但子进程还在运行的子进程就称为孤儿进程(Orphan Proces ...
- 【Linux系统编程】特殊进程之僵尸进程
00. 目录 文章目录 00. 目录 01. 僵尸进程概述 02. 僵尸进程案例 03. 避免僵尸进程 04. 附录 01. 僵尸进程概述 进程已运行结束,但进程的占用的资源未被回收,这样的进程称为僵 ...
- Linux Shell编程笔记8 进程
进程查看机制 ps (process state缩写)命令是进程查看命名,用于Linux系统中所有的进程查看.在当前文件夹下执行如下命令ps,显示: PID TTY TIME CMD7784 pts/ ...
- Linux -- 多进程编程之 - 守护进程
内容概要 一.守护进程概述 二.守护进程创建 2.1.创建子进程,父进程退出 2.2.在子进程中创建新会话 2.2.1.进程组和会话期 2.2.2.setsid()函数说明 2.3.改变当前工作目录 ...
- 面试-操作系统-进程管理-进程-进程调度-死锁
文章目录 ==概念== 备注 简单说下你对并发和并行的理解? 同步.异步.阻塞.非阻塞的概念? 操作系统概念? 一个程序从开始运行到结束的完整过程,你能说出来多少? 用户态和内核态是如何切换的? 什么 ...
- linux网络编程(四)线程池
linux网络编程(四)线程池 为什么会有线程池? 实现简单的线程池 为什么会有线程池? 大多数的服务器可能都有这样一种情况,就是会在单位时间内接收到大量客户端请求,我们可以采取接受到客户端请求创建一 ...
- Linux系统编程(八)线程
Linux系统编程(八)线程 一.什么是线程? 二.Linux内核线程实现原理 线程共享资源 线程非共享资源 线程优缺点 线程控制原语 一.什么是线程? LWP:light weight proces ...
最新文章
- 最后期限已至,高通收购恩智浦全剧终!中国一刀切断高通物联网全局梦!
- [New Portal]Windows Azure Virtual Machine (8) Virtual Machine高可用(上)
- c++ argmax
- python书籍推荐知乎-python入门书籍(爬虫方面)有哪些推荐?
- udhcp server端源码分析1--文件组织结构
- JZOJ 5924. 【NOIP2018模拟10.23】Queue
- 详解C++17下的string_view
- P3455 [POI2007]ZAP-Queries
- SSH中为什么action需要用多例而dao层和service层为什么就用单例就可以
- python编译器_11 个最佳的 Python 编译器和解释器
- [LibTorch] C++ 调用 PyTorch 导出的模型
- nginx集群,带负载均衡(监听多个端口),超详细,轮询分发
- win10主题美化(单)
- 软件开发实训(720科技)――第五课:前端css规范
- Word怎么在方框里打勾
- 数据系统架构-5.实时离线统计系统
- 海康萤石云 H5移动端和PC端云播放本地监控摄像头
- 深度:从 Office 365 新图标来看微软背后的设计新理念
- element组件树形控件el-tree点击展开节点,节点重影
- 作为程序员,我到底在恐慌什么!
热门文章
- 全网首发:怎样制作CDKEY(5)-让CDKEY更混乱
- 删除一个空目录的JAVA代码
- error: passing ‘const AppJniCommand’ as ‘this’ argument discards qualifiers [-fpermissive]
- 如何给硬盘分1T整数的空间
- 由蔺相如谈谈某些人的娱乐表演
- python smooth函数_利用Python程序完成ABAQUS中的一些重复性操作
- 世界上没有一模一样的东西_免费是世界上最昂贵的东西
- 北通usb手柄_多平台适配,北通斯巴达2无线版手柄操控灵敏
- python编写交互界面设计_第16 p,PYthon中的用户交互,Python GUI编程
- python如何输入n个数字_python如何一次性输入多个数