C++ advance(五)Linux 进程和线程 --1. 进程控制

一、进程的相关概念

1. 程序和进程

程序

编译好的二进制文件

进程

运行中的程序
站在程序员的角度:运行一系列指令的过程
站在操作系统的角度:分配系统资源 (CPU 和内存) 的基本单位

区别

程序占用磁盘,不占用系统资源
进程占用系统资源
一个程序对应多个进程,一个进程对应一个程序
程序没有生命周期,进程有生命周期

2. 并发和并行

并发,在一个时间段内,在同一个 cpu 上,同时运行多个程序。
并行,指两个或两个以上的程序在同一时刻发生 (需要有多颗 CPU)

3. 单道 / 多道程序设计

单道:DOS
多道:多个进程,微观上串行,宏观上并行

4.CPU 和 MMU


举例:32 位的电脑,256M 的物理内存虚拟出 4G 的寻址空间

MMU 的功能:
(1) 虚拟内存和物理内存的映射
(2) 修改内存访问级别
用户空间映射到物理内存是独立的,否则对应到相同虚拟地址的两个不同的进程,如果映射到相同的物理地址,进程 1 的用户可以操控进程 2。但是内核空间是映射到同样地址,否则进程间就没办法通信。

5. 进程状态转换

6. 进程控制块 PCB

每个进程在内核中都有一个进程控制块 (PCB) 来维护进程相关的信息,每个进程的 PCB 都是被 MMU 映射到物理内存上某块区域的不同地方。
Linux 内核的进程控制块是 task_struct 结构体。
/usr/src/linux-headers-3.16.0-30/include/linux/sched.h 文件可以查看 struct task_struct

1
sudo grep -rn "struct task_struct {" /usr/

小技巧:光标停在 {上,按 % 键,可以到达结构体的结尾。
struct 结构体定义。其内部成员有很多,重点掌握以下部分即可:

(1) 进程 id。系统中每个进程有唯一的 id,在 C 语言中用 pid_t 类型表示,其实就是一个非负整数。

可用 ps aux 查看第二列即为进程 id。

(2) 进程的状态,有就绪、运行、挂起、停止等状态。
(3) 进程切换时需要保存和恢复的一些 CPU 寄存器。
(4) 描述虚拟地址空间的信息。

就是说 MMU 在负责将虚拟内存映射到物理内存后,需要保存该映射的记录,但是 MMU 不会帮你记录,它只负责映射,记录是放在进程控制块 PCB 当中。

(5) 描述控制终端的信息。

即描述该进程是否与终端进程相关。有些进程与终端有关有些无关,例如守护进程就是与终端无关。

(6) 当前工作目录(Current Working Directory)

当前工作目录就是当前进程的工作目录。例如进程 bash 即我们的输入终端,假设当前目录为家目录,当执行 cd … 后,进程 bash 的工作目录就改变了,那么 PCB 就会记录下来。

(7) umask 掩码

umask 掩码就是一个进程对该进程内部资源分配的默认权限。由于 PCB 每个进程都有且不同,所以 PCB 内部的 umask 掩码在每个进程也不同,并非进程间共享。所以一个进程的 umask 掩码改变并不会影响另一个进程的 umask 掩码。

(8) 文件描述符表,包含很多指向 file 结构体的指针

文件描述符:在 linux 系统中打开文件就会获得文件描述符,它是个很小的非负整数。每个进程在 PCB(Process Control Block)中保存着一份文件描述符表,文件描述符 (FD) 就是这个表的索引,每个表项都有一个指向已打开文件的指针。

(9) 和信号相关的信息
(10) 用户 id 和组 id
(11) 会话(Session)的进程组

会话 id 表示它属于哪个会话,进程组 id 表示该进程所属的进程组。
会话:多个进程组的集合。
进程组:多个进程的集合。

(12) 进程可以使用的资源上限(Resource Limit)

可以使用 ulimit -a 查看

二、环境变量

1. 环境变量

环境变量,是指在操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征:
(1)字符串 (本质)
(2)有统一的格式:名 = 值 [: 值]
(3)值用来描述进程环境信息。
存储形式:与命令行参数类似。char *[] 数组,数组名 environ,内部存储字符串,NULL 作为结尾。
使用形式:与命令行参数类似。
加载位置:与命令行参数类似。位于用户区,高于 stack 的起始位置。
引入环境变量表:须声明环境变量 extern char ** environ

env 查看全部环境变量
echo $HOME
echo $PATH

2. 常见环境变量

环境变量定义了进程的运行环境,一些比较重要的环境变量的含义如下:

PATH

可执行文件的搜索路径。ls 命令也是一个程序,执行它不需要提供完整的路径名 /bin/ls,然而通常我们执行当前目录下的程序 a.out 却需要提供完整的路径名./a.out,这是因为 PATH 环境变量的值里面包含了 ls 命令所在的目录 /bin,却不包含 a.out 所在的目录。PATH 环境变量的值可以包含多个目录,用:号隔开。在 Shell 中用 echo 命令可以查看这个环境变量的值:echo $PATH

SHELL

当前 Shell,它的值通常是 /bin/bash。

TERM

当前终端类型,在图形界面终端下它的值通常是 xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。

LANG

语言和 locale,决定了字符编码以及时间、货币等信息的显示格式。

HOME

当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置。

3.getemv 函数:获取函数变量

1
 函数原型:char \*getenv (const char* name)
1
print ("homepath is [% s]\n", getenv ("HOME"));

4.setenv 函数

也可以用命令 export key=value
在 shell 中执行程序时,shell 会提供一组环境变量。终端关闭或重启以后,环境变量会重置,即 ecport 设置的环境变量只在当前 shell 行下有作用。若想要开机自动加载某环境变量避免设置,需对 /etc/re.local 目录文件改动。

5.unsetenv 函数

linux 环境变量文件区别 & 加载顺序

三、进程控制

1.fork 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main ()
{
printf ("Begin ....\n");
pid_t pid = fork ();
printf ("End ....\n");

if (pid < 0)
{
// 若当前系统资源耗尽,无法再申请子进程,则发送错误
perror ("fork err");
exit (1);
}

if (pid == 0)
{
// 子进程
printf ("I am a child, pid = % d, ppid = % d", getpid (), getppid ());
}

else if (pid > 0)
{
// 父进程
printf ("I am a father, childpid = % d, self = % d, ppid = % d\n", pid, getpid (), getppid ());
// 这样执行之后会出问题,因为父进程先死了,造成孤儿进程。
// 解决办法:让子进程先死
//sleep (1);
}

return 0;
}

如何区分父子进程?
通过 fork 函数的返回值
父子进程的执行顺序?
不一定,哪个进程先抢到 CPU,哪个进程就先执行

2.getpid、getppid 函数

getpid 得到当前进程的 PID
getppid 得到当前进程的父进程的 PID

3.ps 命令和 kill 命令

(1) ps 查看进程信息

-a:(all) 当前系统所有用户的进程
-u:查看进程所有者及其他一些信息
-x:显示没有控制终端的进程 — 不能与用户进行交互的进程【输入、输出】
-j:列出与作业控制相关的信息
可以搭配 grep 使用
例如:ps aux
例如:ps ajx 可以追述进程之间的血缘关系

(2) kill 给进程发送一个信号

SIGKILL 9 号信号
kill -l 查看系统有哪些信号
kill -9 pid 杀死某个线程

4.getuid、getgid 函数

5. 循环创建多个子进程

要求:(1) 多个子进程是兄弟关系。 (2) 判断子进程是第几个子进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main ()
{
int n = 5;
pid_t pid = 0;
for (int i=0; i<5; i++)
{
pid = fork ();
if (pid == 0)
{
printf ("I am child, pid=% d, ppid=% d\n", getpid (), getppid ());
// 解决办法 break;
}
else if (pid > 0)
printf ("I am father, pid=% d, ppid=% d\n", getpid (), getppid ());
}

while (1)
sleep (1);

return 0;
// 这样的写法会造成子进程继续生子进程,无穷无尽,不是本来想要的 5 个子进程的结果
}

6. 进程共享

四、exec 函数族

1.execlp 函数

2.execl 函数

3.execvp 函数

fork/getpid/getppid 函数

ps/kill 函数

excel/execlp 系列函数

孤儿进程,僵尸进程

wait 函数

waitpid 函数

C++ advance 文章总览

C++ advance(四)Linux 命令基础 —1.Linux 常用命令
C++ advance(四)Linux 命令基础 —2.vim 和 gcc 和 library
C++ advance(四)Linux 命令基础 —3.makefile 和 gdb 和 IO
C++ advance(四)Linux 命令基础 —4.stat 和 readdir 和 dup2
C++ advance(五)Linux 进程和线程 —1. 进程控制
C++ advance(五)Linux 进程和线程 —2. 进程间通信
C++ advance(五)Linux 进程和线程 —3. 信号
C++ advance(五)Linux 进程和线程 —4. 进程和线程
C++ advance(五)Linux 进程和线程 —5. 线程同步
C++ advance(六)Linux 高并发网络编程开发 —1. 网络编程基础 socket
C++ advance(六)Linux 高并发网络编程开发 —2.tcp 三次握手 - 并发
C++ advance(六)Linux 高并发网络编程开发 —3.tcp 状态转换 - selcet poll
C++ advance(六)Linux 高并发网络编程开发 —4.epoll udp
C++ advance(六)Linux 高并发网络编程开发 —5. 广播 - 组播 - 本地套接字
C++ advance(六)Linux 高并发网络编程开发 —6.libevent
C++ advance(六)Linux 高并发网络编程开发 —7.xml json
C++ advance(七)Linux 高并发 web 服务器开发 —1.
C++ advance(七)Linux 高并发 web 服务器开发 —2.
C++ advance(七)Linux 高并发 web 服务器开发 —3.