基本概念

程序和进程的区别

程序是平台相关的二进制文件,只占用磁盘空间。编写完程序代码后,编译为可执行的二进制文件即可。

进程是运行中的程序,占用 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 应用编程】进程管理 - 进程、线程和程序相关推荐

  1. 【Linux 内核】进程管理 ( 内核线程概念 | 内核线程、普通进程、用户线程 | 内核线程与普通进程区别 | 内核线程主要用途 | 内核线程创建函数 kernel_thread 源码 )

    文章目录 一.内核线程概念 二.内核线程.普通进程.用户线程 三.内核线程.普通进程区别 四.内核线程主要用途 五.内核线程创建函数 kernel_thread 源码 一.内核线程概念 直接 由 Li ...

  2. Linux系统编程 74 孤儿进程和僵尸进程

    Linux系统编程  74 孤儿进程和僵尸进程 学习笔记 孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 init进程,称为init进程领养了孤儿进程. init进程会去接替 ...

  3. 【Linux系统编程】特殊进程之守护进程

    00. 目录 文章目录 00. 目录 01. 守护进程概述 02. 守护进程查看方法 03. 编写守护进程的步骤 04. 守护进程代码 05. 附录 01. 守护进程概述 守护进程(Daemon Pr ...

  4. 【Linux系统编程】特殊进程之孤儿进程

    00. 目录 文章目录 00. 目录 01. 孤儿进程概述 02. 孤儿进程代码 03. 附录 01. 孤儿进程概述 父进程运行结束,但子进程还在运行的子进程就称为孤儿进程(Orphan Proces ...

  5. 【Linux系统编程】特殊进程之僵尸进程

    00. 目录 文章目录 00. 目录 01. 僵尸进程概述 02. 僵尸进程案例 03. 避免僵尸进程 04. 附录 01. 僵尸进程概述 进程已运行结束,但进程的占用的资源未被回收,这样的进程称为僵 ...

  6. Linux Shell编程笔记8 进程

    进程查看机制 ps (process state缩写)命令是进程查看命名,用于Linux系统中所有的进程查看.在当前文件夹下执行如下命令ps,显示: PID TTY TIME CMD7784 pts/ ...

  7. Linux -- 多进程编程之 - 守护进程

    内容概要 一.守护进程概述 二.守护进程创建 2.1.创建子进程,父进程退出 2.2.在子进程中创建新会话 2.2.1.进程组和会话期 2.2.2.setsid()函数说明 2.3.改变当前工作目录 ...

  8. 面试-操作系统-进程管理-进程-进程调度-死锁

    文章目录 ==概念== 备注 简单说下你对并发和并行的理解? 同步.异步.阻塞.非阻塞的概念? 操作系统概念? 一个程序从开始运行到结束的完整过程,你能说出来多少? 用户态和内核态是如何切换的? 什么 ...

  9. linux网络编程(四)线程池

    linux网络编程(四)线程池 为什么会有线程池? 实现简单的线程池 为什么会有线程池? 大多数的服务器可能都有这样一种情况,就是会在单位时间内接收到大量客户端请求,我们可以采取接受到客户端请求创建一 ...

  10. Linux系统编程(八)线程

    Linux系统编程(八)线程 一.什么是线程? 二.Linux内核线程实现原理 线程共享资源 线程非共享资源 线程优缺点 线程控制原语 一.什么是线程? LWP:light weight proces ...

最新文章

  1. 最后期限已至,高通收购恩智浦全剧终!中国一刀切断高通物联网全局梦!
  2. [New Portal]Windows Azure Virtual Machine (8) Virtual Machine高可用(上)
  3. c++ argmax
  4. python书籍推荐知乎-python入门书籍(爬虫方面)有哪些推荐?
  5. udhcp server端源码分析1--文件组织结构
  6. JZOJ 5924. 【NOIP2018模拟10.23】Queue
  7. 详解C++17下的string_view
  8. P3455 [POI2007]ZAP-Queries
  9. SSH中为什么action需要用多例而dao层和service层为什么就用单例就可以
  10. python编译器_11 个最佳的 Python 编译器和解释器
  11. [LibTorch] C++ 调用 PyTorch 导出的模型
  12. nginx集群,带负载均衡(监听多个端口),超详细,轮询分发
  13. win10主题美化(单)
  14. 软件开发实训(720科技)――第五课:前端css规范
  15. Word怎么在方框里打勾
  16. 数据系统架构-5.实时离线统计系统
  17. 海康萤石云 H5移动端和PC端云播放本地监控摄像头
  18. 深度:从 Office 365 新图标来看微软背后的设计新理念
  19. element组件树形控件el-tree点击展开节点,节点重影
  20. 作为程序员,我到底在恐慌什么!

热门文章

  1. 全网首发:怎样制作CDKEY(5)-让CDKEY更混乱
  2. 删除一个空目录的JAVA代码
  3. error: passing ‘const AppJniCommand’ as ‘this’ argument discards qualifiers [-fpermissive]
  4. 如何给硬盘分1T整数的空间
  5. 由蔺相如谈谈某些人的娱乐表演
  6. python smooth函数_利用Python程序完成ABAQUS中的一些重复性操作
  7. 世界上没有一模一样的东西_免费是世界上最昂贵的东西
  8. 北通usb手柄_多平台适配,北通斯巴达2无线版手柄操控灵敏
  9. python编写交互界面设计_第16 p,PYthon中的用户交互,Python GUI编程
  10. python如何输入n个数字_python如何一次性输入多个数