进程与程序
进程是计算机中处于运行中程序的实体。程序本身只是指令、数据及其组织形式的描述,进程才是程序(指令和数据)的真正运行实例。
多个进程可与同一个程序相关联,而每个进程则是以同步或异步的方式独立运行的。
Linux的进程结构
在
L
i
n
u
x
Linux
Linux中,进程结构一般由3部分组成:代码段、数据段和堆栈段
。
- 代码段:用于存放程序代码的数据
- 数据段:存放程序的全局变量、常量和静态变量
- 堆栈段:其中的栈用于函数调用,它存放着函数的参数、函数内部定义的局部变量,还包括了进程控制块 P C B PCB PCB
PCB(进程控制块)
关于 P C B PCB PCB,有以下几个特点:
- P C B PCB PCB处于进程核心堆栈的底部,不需要额外分配空间
- P C B PCB PCB是进程存在的唯一标识,系统通过 P C B PCB PCB的存在而感知进程的存在
- 系统通过 P C B PCB PCB对进程进行管理和调度
- P C B PCB PCB包括创建进程、执行程序、退出进程以及改变进程的优先级等
程序->进程
1)内核将程序读入内存,为程序分配内存空间
2)内核为该进程分配进程标识符(
P
I
D
PID
PID)和其他所需资源
3)内核为进程保存
P
I
D
PID
PID及相应的状态信息,把进程放到运行队列中等待执行,程序转化为进程后就可以被操作系统的调度程序调度执行了
程序是指可运行的二进制代码文件,把这种文件加载到内存中运行就得到了一个进程。同一个程序文件可以被加载多次成为不同的进程。因此,进程与进程标识符之间是一对一的关系,而与程序文件之间是多对一的关系
PID(进程标识符)
每个进程在系统中都有唯一的一个 I D ID ID标识它,这个 I D ID ID就是进程标识符( P I D PID PID)。因为其唯一性,所以系统可以根据它准确定位到一个进程。进程标识符的类型为 p i d pid pid_ t t t,其本质是一个无符号整型的类型别名。
进程的创建与结束
进程的创建有两种方式:一种是由操作系统创建,一种是由父进程创建。
系统启动时,操作系统会创建一些进程,它们承担着管理和分配系统资源的任务,这些进程通常被称为系统进程。
操作系统创建进程的过程可描述为:
0号进程–>1号内核进程–>1号内核线程–>1号用户进程(init进程)–>getty进程–>shell进程
1号内核进程调用执行init并演变成1号用户态进程(init进程),这里前者init是函数,后者是进程
区别:
- (1)init()函数在内核态运行,是内核代码
- (2)init进程是内核启动并运行的第一个用户进程,运行在用户态下
- (3)init()函数调用execve()从文件/etc/inittab中加载可执行程序init并执行,这个过程并没有使用调用do_fork(),因此两个进程都是1号进程
进程的创建----fork()函数
fork()函数概念
L i n u x Linux Linux系统允许任何一个用户进程创建一个子进程,创建成功后,子进程将存在于系统之中,并且独立于父进程。
L i n u x Linux Linux系统下使用 f o r k fork fork()函数创建一个子进程,其函数原型如下:
#include <unistd.h>
pid_t fork(void);
父进程与子进程:除了0号进程以外, L i n u x Linux Linux系统中的任何一个进程都是由其他进程创建的。创建新进程的进程,即调用 f o r k fork fork()函数的进程就是父进程,而新创建的进程就是子进程.
fork()函数返回值
f o r k fork fork()函数不需要参数,返回值是一个进程标识符( P I D PID PID).对于返回值,有以下三种情况:
- (1) 对于父进程, f o r k fork fork()函数返回新创建的子进程的 I D ID ID
- (2) 对于子进程, f o r k ( ) fork() fork()函数返回0
- (3) 如果创建出错,则 f o r k ( ) fork() fork()函数返回-1
fork()函数作用
f o r k fork fork()函数会创建一个新的进程,并从内核中为此进程分配一个新的可用的进程标识符( P I D PID PID),之后,为这个新进程分配进程空间,并将父进程的进程空间中的内容复制到子进程的进程空间中,包括父进程的数据段和堆栈段,并且和父进程共享代码段。
由于在复制时复制了父进程的堆栈段,所以两个进程都停留在了 f o r k fork fork()函数中,等待返回。因此, f o r k fork fork()函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的
例1:创建一个子进程
/*************************************************************************
> File Name: fork.cpp
> Author: ersheng
> Mail: ershengaaa@163.com
> Created Time: Fri 22 Feb 2019 03:13:57 PM CST
************************************************************************/
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
using namespace std;
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fail to fork");
exit(-1);
}
else if (pid == 0) {
printf("Sub-process, PID: %u, PPID: %u\n", getpid(), getppid());
}
else {
printf("Parent, PID: %u, Sub-process PID: %u\n", getpid(), pid);
sleep(2);
}
return 0;
}
例2:探讨父子进程的共享资源
/*************************************************************************
> File Name: fork1.cpp
> Author: ersheng
> Mail: ershengaaa@163.com
> Created Time: Fri 22 Feb 2019 03:20:21 PM CST
************************************************************************/
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
using namespace std;
int global = 1; //初始化的全局变量,存在data段
int main() {
pid_t pid;
int stack = 1; //局部变量,存在栈中
int *heap; //指向堆变量的指针
heap = (int *)malloc(sizeof(int));
*heap = 3; //设置堆中的值为3
pid = fork();
if (pid < 0) {
perror("fail to fork");
exit(-1);
}
else if (pid == 0) {
//子进程,改变变量的值
global++;
stack++;
(*heap)++;
printf("In sub-process, global: %d, stack: %d, heap: %d\n", global, stack, *heap);
exit(0);
}
else {
sleep(2);
printf("In parent-process, global: %d, stack: %d, heap: %d\n", global, stack, *heap);
}
return 0;
}
从执行的结果来看:子进程对这些数据段和堆栈段中内容的修改并不会影响到父进程的进程环境。
子进程完全复制了父进程的地址空间的内容,包括堆栈段和数据段的内容。子进程和父进程共用代码段。(父进程的资源大部分被 f o r k fork fork()函数所复制,只有小部分是子进程与父进程是不同的。具体哪些资源就不在这里详细说明了,想要了解的同学可以自行百度)
现在的Linux内核在实现 f o r k fork fork()函数往往在创建子进程时并不会立即复制父进程的数据段和堆栈段,而是当子进程修改这些数据内容时复制操作才会发生,达到一个“写时复制”的作用
进程的结束----exit()函数
exit()函数概念
当一个进程需要退出时,需要调用退出函数。Linux环境下使用 e x i t exit exit()函数退出进程,具体函数原型如下:
#include<stdlib.h>
void exit(int status);
e x i t exit exit()函数的参数表示进程的退出状态,这个状态的值是一个整型,保存在全局变量$?中;可以通过 s h e l l shell shell得到已结束进程的结束状态,执行“echo $?"命令即可
$?是 L i n u x Linux Linux s h e l l shell shell中的一个内置变量,其中保存的是最近一次运行的进程的返回值,返回值的情况有以下三种:
- 1)程序的 m a i n main main函数运行结束,$?中保存 m a i n main main函数的返回值
- 2)程序运行中调用 e x i t exit exit函数结束运行,$?中保存 e x i t exit exit函数的参数
- 3)程序异常退出,$?中保存异常出错的错误号
例3:测试从$?中获得进程的返回值
/*************************************************************************
> File Name: get.cpp
> Author: ersheng
> Mail: ershengaaa@163.com
> Created Time: Sat 23 Feb 2019 09:29:07 PM CST
************************************************************************/
#include<iostream>
using namespace std;
int main() {
int i;
while (1) {
cin >> i;
if (i == 0) {
break;
}
}
return 10;
}
结果捕捉到
m
a
i
n
main
main函数的返回值10
进程退出的方式
一、在 L i n u x Linux Linux中进程退出分为正常退出和异常退出两种方式:
1.正常退出
- 1)在 m a i n main main()函数中执行return
- 2)调用 e x i t exit exit()函数
- 3)调用_ e x i t exit exit()函数
2.异常退出
- 1)调用 a b o r t abort abort函数
- 2)进程收到某个信号,而该信号使程序终止
二、以上几种退出方式的不同点:
1. e x i t exit exit和 r e t u r n return return的区别
- e x i t exit exit是一个函数,带有参数, e x i t exit exit执行完后把控制权交给系统
- r e t u r n return return是函数执行完后的返回, r e t u r n return return执行完后把控制权交给调用函数
2. e x i t exit exit和 a b o r t abort abort的区别
- e x i t exit exit是正常终止进程
- a b o r t abort abort是异常终止
exit()和_exit()函数
1) e x i t exit exit()和_ e x i t exit exit()函数都是用来终止进程的,当程序执行到 e x i t exit exit或_ e x i t exit exit时,系统无条件地停下剩下所有操作,清除包括 P C B PCB PCB在内地各种数据结构,并终止本进程地运行
2) e x i t exit exit()是在头文件 s t d l i b . h stdlib.h stdlib.h中声明,而_ e x i t exit exit()声明在头文件 u n i s t d . h unistd.h unistd.h中。 e x i t exit exit中地参数 e x i t exit exit c o d e code code为0时,代表进程正常终止,若为其他值,表示程序执行过程中有错误发生; e x i t exit exit()执行后立即返回给内核,而 e x i t exit exit()要先执行清除操作,然后将控制权交给内核
3) e x i t exit exit()函数与_ e x i t exit exit()最大区别在于 e x i t exit exit()函数在调用 e x i t exit exit系统之前要检查文件地打开请况,把文件缓冲区地内容写回文件。因此,要保证数据地玩完整性,就一定要使用 e x i t exit exit()函数
例4: e x i t exit exit()和_ e x i t exit exit的区别
使用 e x i t exit exit()函数:
/*************************************************************************
> File Name: exit.cpp
> Author: ersheng
> Mail: ershengaaa@163.com
> Created Time: Sat 23 Feb 2019 09:51:10 PM CST
************************************************************************/
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int main() {
printf("using exit.\n");
printf("This is the content in buffer");
exit(0);
}
使用_ e x i t exit exit()函数:
/*************************************************************************
> File Name: _exit.cpp
> Author: ersheng
> Mail: ershengaaa@163.com
> Created Time: Sat 23 Feb 2019 09:54:33 PM CST
************************************************************************/
#include<iostream>
#include <stdio.h>
#include <unistd.h>
using namespace std;
int main(){
printf("using _exit.\n");
printf("This is the content in buffer");
_exit(0);
}
e
x
i
t
.
c
p
p
exit.cpp
exit.cpp与_
e
x
i
t
.
c
p
p
exit.cpp
exit.cpp中,只有
e
x
i
t
exit
exit(0)与_
e
x
i
t
exit
exit(0)不同,其中的
p
r
i
n
t
f
printf
printf函数就是使用缓冲
I
/
O
I/O
I/O的方式,该函数在遇到”\n"换行符时自动的从缓冲区中将记录读出。程序结果显示,
e
x
i
t
exit
exit()函数会将缓冲区的数据写完后才退出,而_
e
x
i
t
exit
exit()函数直接退出