概念
多用户多任务的操作系统,必须对程序能够并行执行。
核心的概念就是进程,或者说进程是操作系统资源管理的最小单位。
操作系统通过进程来管理计算机的软,硬件资源。
进程和程序的区别: 进程是动态的实体,是程序的一次执行过程;程序是静态的,是保存在硬盘上的代码。
线程: 在进程内又划分了许多线程,它是比进程更小的,能独立运行的基本单位。它与同一个进程中的其他线程共享进程的全部资源。
进程在执行过程中拥有独立的内存空间,而进程所属的内部线程共享这些内存。
一个线程可以创建或者撤销另一个线程,同一个进程中的多个线程可以并行执行。
在linux下可以使用ps或者pstree来查看当前系统中的进程。
进程标识
linux中,每个进程都有一个唯一的进程ID标识,进程ID是一个非负数,每个进程除了进程ID之外,还有一些其他的标识信息。
在unistd.h头文件中:
函数声明 | 功能 |
---|---|
getpid(id) | 获得进程ID |
getppid(id) | 获得进程的父进程ID |
getuid() | 获得进程的实际用户ID |
geteuid() | 获得进程的有效用户ID |
getgid() | 获得进程的实际组ID |
getegid(id) | 获得进程的有效组ID |
进程结构
一个进程由3个部分组成:代码段,数据段和堆栈段。
代码段存放程序的可执行代码;数据段存放程序的全局变量、常量、静态变量; 堆用来存放动态分配的变量;栈用来存放函数调用,如函数的参数、函数内部的局部变量。
进程的有以下几种状态:
①运行状态(R): 进程正在运行或在运行队列中等待运行。
②可中断等待状态(S):进程正在等待某个事件完成(如等待数据到达),等待过程可以被信号或定时器唤醒。
③不可中断等待状态(D):进程正在等待某个事件完成,但不能被信号或定时器唤醒,必须等待到事件完成。
④僵死状态(Z):进程已终止,但进程描述符依然存在,直到父进程调用wait()函数后释放。
⑤停止状态(T):进程因为收到SIGSTIP、SIGSTP、SIGTIN、SIGTOU信号后停止运行或者该进程正在被跟踪(调试程序时,进程处于被跟踪状态)。
用ps命令来查看进程的一些信息
还有一些后缀字符 PID为1的进程,大S为中断等待状态,小s说明该进程为会话首进程。
大I 说明是多线程的进程
<符号说明是高优先级进程,同样N代表低优先级进程,L代表内存锁页,即页不可以被换出内存。
在本机上的执行结果也说明,进程ID不一定就是连续的。
进程的内存映像
程序转化为进程:
①内核把可执行程序读入内存(复制),为程序分配内存空间。
②内核为该程序分配进程标识符(PID)和其他所需资源。
③内核为该程序保存PID及相应的状态信息,把程序放进运行队列中等待执行。
当内核把可执行程序读入内存后,一般布局如图所示:
代码段:即二进制机器代码,代码段是只读的,可以被多个进程共享。如果一个进程创建了一个子进程,父子进程共享代码段,此外子进程还获得父进程数据段、堆、栈的复制(不共享)。
数据段:存储已被初始化的变量,包括全局变量和已初始化的静态变量。
未初始化数据段:存储未被初始化的静态变量,也称为bss段。
堆: 动态分配的变量。
栈:用于函数调用,保持函数的返回值,函数的参数、函数内部的局部变量。
创建进程
创建进程有两种方式,一是由操作系统创建;二是由父进程创建。
由操作系统创建的进程,它们之间是平等的,没有继承关系。
由父进程创建的进程,它是子进程,子进程又可以创建进程,形成一个进程家族。
fork函数是创建一个新进程的唯一方法,这样创建出的进程自然就是子进程。
当创建了一个子进程后,父进程和子进程争夺cpu,抢到cpu者执行,另一个挂起等待。
#include <unistd.h> pid_t fork(void);
一般情况下,函数最多有一个返回值,但fork函数非常特殊,它有两个返回值。
成功调用fork后,当前进程实际上分裂为两个进程,一个返回值是创建的子进程ID,另一个返回值是0.
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void error_exit(const char * pname);
int main()
{
pid_t pid;
pid=fork();
if(pid==-1)
{
error_exit("open:");
}
if(pid==0)
{
printf("succes create child process\n");
}
else
{
printf("the child process pid is :%d\n",pid);
}
return 0;
}
void error_exit(const char * pname)
{
perror(pname);
exit(1);
}
if语句只写了一次,但实际运行了两次,这就是因为父进程运行一次,子进程运行一次。
通过返回值,就能区分到底是哪个进程在运行了,返回值为0,代表子进程在运行。如果返回值大于0,代表父进程在运行。
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void error_exit(const char * pname);
int main()
{
pid_t pid;
pid=fork();
if(pid==-1)
{
error_exit("open:");
}
if(pid==0)
{
//printf("succes create child process \n");
printf("child process pid is running %d\n",getpid());
}
else
{
printf("the child process pid is :%d\n",pid);
printf("parent process pid is running %d\n",getpid());
}
return 0;
}
void error_exit(const char * pname)
{
perror(pname);
exit(1);
}
注意这里有个弯绕,父进程在运行时,pid是子进程的ID,但是getpid()是当前进程的ID,也即父进程ID
我的运行结果都是父进程先运行,但具体谁先运行时不确定的,取决于内核所使用的调度算法。
把判断改成switch格式
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void error_exit(const char * pname);
int main()
{
pid_t pid;
pid=fork();
switch (pid)
{
case -1:
error_exit("process");
break;
case 0:
printf("child process pid is running %d\n",getpid());
break;
default:
printf("parent process pid is running %d\n",getpid());
break;
}
return 0;
}
void error_exit(const char * pname)
{
perror(pname);
exit(1);
}