进程是什么?
进程是一个程序执行的实例或者一个正在运行的程序,从linux内核角度分析,是在内核中创建了一个task_struct结构体来描述进程,该结构体中比较重要的有:进程PID(描述本进程的唯一标识)、进程状态(运行、睡眠、停止、僵尸)、内存指针(指向进程的虚拟地址空间)、程序计数器(报存程序将要执行的下一条指令的地址)等,所以称进程是操作系统分配资源的基本单位。
cpu密集型程序:程序大量的时间都在用CPU进行计算;
IO密集型程序:程序大量的时间都在和磁盘打交道;
并行:在同一时间,多个进程,每一个进程都拥有一个cpu进行计算;
并发:多个进程,只有少量的cpu,每个进程只能独占cpu一小会,就会让出cpu供其他进程运算;
程序:(program)是存放在磁盘文件中的可执行文件;
进程:(process)是正在执行的(动态的)程序,占用系统资源,在内存中执行。
进程和程序的区别:
每个进程在内核中都有一个进程控制块(PCB,process control block)来维护进程的相关信息,Linux内核的进程控制块是task_struct结构体。
查看task_struct结构体:/usr/src/kernels/3.10.0-1160.6.1.el7.x86_64/include/linux,其中3.10.0-1160.6.1.el7.x86_64是版本信息,不同版本有所不同。
task_struct中主要内容:
内核使用双向链表来组织进程的task_struct信息。
当一个程序启动,操作系统会维护一个task_struct结构体,并在**/proc/[pid]**文件夹保存与当前进程相关的文件。当进程运行的时候,这个文件会存在,当进程退出的时候,就会被操作系统所清理。
task_struct结构体中一个条目指向mm_struct,他描述了虚拟内存的当前状态,其中的两个字段,pgd指向第一级页表(页的全局目录)的基址,而mmap指向一个vm_area_struct的链表,其中每一个都描述了当前虚拟地址空间中的一个区域,这个结构主要包含的字段:区域的起始处、区域的结束处、这个区域内包含的所有页的读写权限、这个区域内的页面是。与其他进程共享的还是这个进程私有的、链表中下一个区域结构。
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。他将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时拷贝。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任意一个后来进行写操作时,写时拷贝机制就会创建新页面。
一个父进程退出,而它的一个或者多个子进程还在运行,那那些子进程将成为孤儿进程。Linux系统中,每当出现一个孤儿进程,内核就会把孤儿进程的父进程设置为init,而init进程会循环的调用wait(),处理已经退出的子进程。
举个栗子:
#include
#include
int main()
{
pid_t pid=fork();
if(pid<0)
{
perror("fork error");
return 0;
}
else if(pid==0)
{
printf("我是子进程,进程id=%d,父进程id=%d\n",getpid(),getppid());
//子进程
while(1)
{
sleep(1);
}
}
else
//父进程
printf("我是父进程,进程id=%d\n",getpid());
sleep(20);
}
return 0;
}
子进程一直在运行,通过ctrl+z 和ctrl+c 无法杀死,通过kill [pid]杀死进程。
一个进程使用fork创建子进程,如果子进程先退出,而父进程并没有调用wait或者waitpid获取子进程的状态信息,那么子进程的进程仍然保存在系统中。
子进程已经退出了,但是它的父进程还没有回收其资源(PCB),从而导致子进程变成僵尸状态(由于子进程的PCB并没有给内核所释放,释放这样的PCB需要父进程来进行回收),那么这个进程就称为僵尸进程。
#include #include
int main()
{
pid_t pid=fork();
if(pid<0)
{
perror("fork error");
return 0;
}
else if(pid==0)
{
//子进程逻辑
printf("我是子进程,进程id=%d,父进程id=%d\n",getpid(),getppid());
sleep(2);
printf("子进程退出\n");
}
else
{
//父进程逻辑
//防止父进程先结束,产生孤儿进程
while(1)
{
printf("我是父进程,进程id=%d\n",getpid());
sleep(1);
}
}
return 0;
}
对于僵尸进程,如果进程不调用wait/waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。
僵尸进程并不是问题的根源,罪魁祸首是产出大量僵尸进程的那个父进程。因此,当我们要消灭僵尸进程时,要做的就是把产生僵尸进程的父进程杀死(通过发送SIGTERM或者SIGKILL信号),杀死父进程之后,产生的僵尸进程就变成了孤儿进程,这些孤儿进程会被init进程接管。
环境变量是指在操作系统中用来指定操作系统运行环境的一些参数。
HOME:保存当前用户主目录的路径
SHELL:保存当前使用的命令行解释器的名称,它的值通常是/bin/bash
PATH:保存可执行程序的路径
LD_LIBRARY_PATH:程序运行时,依赖库文件的搜索路径
CPLUS_INCLUDE_PATH:可以定义第三方C++头文件所在的路径
举个例子:之所我们能在任意路径下使用ls命令,是因为环境变量PATH下包含了可执行程序ls所在的路径。
main函数的参数
int main(int argc,char* argv[],char* env[])
//argc——命令行参数个数
//argv——命令行参数
//env——环境变量
libc库当中的变量来获取
extern char** environ
getenv函数
#include
char *getenv(const char *name);
getenv()用来取得参数name环境变量的内容
name:环境变量的名称
返回值:执行成功则返回指向该内容的指针,找不到符合的环境变量名称则返回NULL