程序是存放在磁盘文件中的可执行文件。程序的执行实例被称为进程,进程具有独立的权限与职责。
每个进程运行在其各自的虚拟地址空间中,进程之间可以通过由内核控制的机制相互通讯。如果系统中某个进程崩溃,它不会影响到其余的进程。
每个Linux进程都一定有一个唯一的数字标识符,称为进程ID,PID(进程ID)总是一个非负整数。
在进程的main函数执行之前内核会启动,编译器在编译的时候会将启动例程编译进可执行文件中。
启动例程的作用:搜集命令行的参数传递给main函数中的argc和argv;搜集环境信息构建环境表并传递给main函数;登记进程的终止函数。
查看系统中的进程
通过下面的命令就可以查看当前系统执行的进程。
ps
ps -ef
ps -aux
ps -ef | more
当前系统执行的进程如下图所示。
USER是指进程的属主;PID:进程ID号;PPID:父进程ID号;%CPU:进程占用的CPU百分比;%MEM:占用内存的百分比;VSZ:进程虚拟大小;RSS:驻留中页的数量;TTY:终端ID;STAT:进程的状态;START:启动进程的时间;TIME:进程消耗CPU的时间;COMMAND:命令的名称和参数。
进程常见的状态:运行状态®、等待状态(S)、停止状态(T)、僵尸状态(Z)。僵尸状态指的是进程终止或结束,但是在进程表项中仍有记录。
进程标识
获取进程相关标识的函数原型如下。
#include <unistd.h>
#include <sys/types.h>
pid_t getpid(void); //获取当前进程ID
uid_t getuid(void); //获得当前进程的实际用户ID
uid_t geteuid(void); //获得当前进程的有效用户ID
gid_t getgid(void); //获得当前进程的用户组ID
pid_t getppid(void); //获得当前进程的父进程ID
pid_t getpgrp(void); //获得当前进程所在的进程组ID
pid_t getpgid(pid_t pid); //获得进程ID为pid的进程所在的进程组ID
关于进程标识相关的代码示例如下。
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("pid : %d\n",getpid());
printf("ppid : %d\n",getppid());
printf("uid : %d\n",getuid());
printf("euid : %d\n",geteuid());
printf("user gid : %d\n",getgid());
printf("gid : %d\n",getpgrp());
printf("pgid : %d\n",getpgid(getpid()));
printf("ppgid : %d\n",getpgid(getppid()));
return 0;
}
程序编译之后的执行结果如下图所示。
进程创建
进程创建使用的头文件及函数原型如下。
#include <unistd.h>
#include <sys/types.h>
pid_t fork(void);
pid_t vfork(void);
进程创建函数调用第一,返回两次,在子进程中返回值为0,在父进程中返回值是子进程的进程ID号。
使用进程创建函数fork()创建子进程,父子进程哪个先运行根据系统调度决定,子进程会复制(继承)父进程的内存空间。
使用进程创建函数vfork()创建子进程,子进程先运行且不复制父进程的内存空间。
子进程继承父进程的属性包括:用户信息和权限、目录信息、信号信息、环境、共享存储段、资源限制、堆、栈和数据段,代码段是父子进程共享的。
子进程的特有属性:进程ID、锁信息、运行时间、未决信号。
操作文件时的内核结构变化:子进程只继承父进程的文件描述表,不继承但共享文件表项和i-node;父进程创建一个子进程后,文件表项中的引用计数器加1变成2,当父进程作close操作后,计数器减1,子进程还是可以使用文件表项,只有当计数器为0时才会释放文件表项。
使用fork函数创建进程的代码示例如下。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
printf("当前运行进程的ID : %d\n",getpid());
pid = fork();
if(pid < 0)
perror("Error!\n");
else if(pid == 0)
printf("子进程在运行!fork返回值:%d,当前进程ID:%d,父进程ID:%d\n",pid,getpid(),getppid());
else
printf("父进程在运行!fork返回值:%d,当前进程ID:%d,父进程ID:%d\n",pid,getpid(),getppid());
printf("当前运行进程的ID : %d\n",getpid());
sleep(1);
return 0;
}
上面程序编译后运行结果如下图所示。
可以清楚的看到,在进程创建完毕后,系统中就有了两个进程,一句打印输出的代码被父子进程各执行了一次!
父进程和通过fork()函数创建出的子进程有相同的虚拟空间,但是却有各自的物理空间!
下面的例子就能够说明这一点。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int global_v = 10;
int main()
{
int area_v = 10;
static int static_v = 10;
pid_t pid;
printf("当前运行进程的ID : %d\n",getpid());
pid = fork();
if(pid < 0)
perror("Error!\n");
else if(pid == 0)
{
global_v = 20;
area_v = 20;
static_v = 20;
printf("子进程中,global_v地址:%p area_v地址:%p static_v地址:%p\n",&global_v,&area_v,&static_v);
printf("子进程在运行!fork返回值:%d,当前进程ID:%d,父进程ID:%d\n",pid,getpid(),getppid());
}
else
{
global_v = 30;
area_v = 30;
static_v = 30;
printf("父进程中,global_v地址:%p area_v地址:%p static_v地址:%p\n",&global_v,&area_v,&static_v);
printf("父进程在运行!fork返回值:%d,当前进程ID:%d,父进程ID:%d\n",pid,getpid(),getppid());
}
printf("当前运行进程的ID : %d, global_v = %d, area_v = %d, static_v = %d\n",getpid(),global_v,area_v,static_v);
return 0;
}
程序编译后的运行结果如下图所示。
可以看到,在父子进程中打印的变量地址是一样的(这里打印的变量地址是虚拟地址,并非物理地址),但是里面存放的值却是不一样的,这说明父子进程有相同的虚拟空间,却各自映射了独立的物理空间。
关于虚拟空间和物理空间,下面是一个例子。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h