进程的介绍

进程的概念

什么是进程

  • 进程是程序的一次执行过程
    程序:是静态的,是存储在外存上的可执行二进制文件
    进程:动态的概念,是程序的一次执行过程,包括了进程的创建,调度,消亡,是存在于内存中的

  • 进程是独立的,可以被CPU调度的任务
    Linux中的调度机制:时间片轮询机制
    操作系统会给每一个进程分配时间片,当时间片结束后,CPU资源会切走,当前进程需要等待下一次调度

  • 进程在被调度的时候,系统会分配和释放各种资源(CPU资源,内存资源,进城调度块(PCB))

进程的五态图

  • 创建态
  • 就绪态
  • 运行态
  • 阻塞态
  • 终止态
    进程运行过程中有5种状态,运行态只是进程运行过程中的一种状态
    进程的介绍_第1张图片

进程的内存分布

内存分布

  • 在Linux操作系统中,每个进程都会被分配4G的内存空间。(虚拟内存)
  • 0~3G是用户空间代码使用,每个进程相互独立
  • 3~4G是内核空间,所有进程共享
  • 每个进程的用户空间都有自己独立的区
    (1)静态存储区(2)堆区(3)栈区
    进程的介绍_第2张图片

虚拟内存和物理内存

虚拟内存和物理内存之间的关系—>映射关系

  • 物理内存:硬件上(内存条上)真正存在的存储空间
  • 虚拟内存:程序启动后,会分配4G的虚拟地址空间,用户只能访问到虚拟地址空间。当要使用的时候,由虚拟地址映射到物理地址上。
    32位操作系统,指针变量的大小为4bytes,取值范围为【0,2^32-1】,所以寻址范围为4G
    64位操作系统,指针变量的大小为8bytes,只取前48位存储地址,所以寻址范围是256TB
  • 虚拟内存的目的:动态分配内存
    进程的介绍_第3张图片

进程是资源分配的最小单位

以进程为单位分配和释放各种资源

  • 内存的申请和释放
  • 文件描述符表:每个进程都有自己的1024个文件描述符
  • 时间片
  • 管理自己的虚拟地址空间,使用的时候映射到物理地址上

进程标识

操作系统会给每一个进程分配一个编号,这个编号就是进程号

  • 主要进程标识:
    1>进程号:PID (process id)
    2>父进程号:PPID (parent process id)
    3>进程组号:PGID(process group id)若干个进程的集合,称之为进程组,默认情况下新创建的进程会继承父进程的组ID
    4>会话组号:SID (session id)若干个进程组的集合,称之为会话组,默认情况下新创建的进程会继承父进程的会话ID
  • 操作系统刚启动的时候,会启动三个进程。三个特殊的进程号:
    1>0:idle进程——操作系统引导程序,创建1号,2号进程
    2>1:init进程——初始化内核的各个模块,当内核启动完成后,用于收养孤儿进程(没有父进程的进程)
    3>2:kthreadd进程——用于进程间调度

进程相关的shell指令

pa -aux

  • 功能:显示进程占计算机资源的百分比

pa -ajx

  • 功能:显示进程之间的关系

pidof

  • 功能:根据进程名字获取PID号;
  • 格式:pidof a.out

pstree

  • 功能:显示进程关系树

kill

  • kill -9 pid ——根据pid号杀死进程
  • killall -9 进程名字 ——根据进程名字杀死进程

进程的STAT

  • D:不能被中断的阻塞状态
  • R:运行状态
  • S:可以被中断的阻塞状态
  • T:被信号控制的挂起状态
  • t:进程被调试的挂起状态
  • X:死亡态,死亡是一瞬间的,该状态永远看不到
  • Z:僵尸状态,当前进程退出后,父进程没有为其收尸
  • <:高优先级的
  • N:低优先级的
  • L:有些页被锁进内存
  • s:会话组组长
  • l:多线程
  • +:运行在前端、

进程相关的函数

fork

  • 功能:创建一个子进程
  • 原型:pid_t fork(void)
  • 返回值:成功,>0,在父进程中,返回创建的子进程的pid号
    =0,在子进程中,返回0;失败,返回-1,更新errno,且没有子进程被创建

注意:

  • fork函数创建的子进程,会克隆父进程用户空间的所有资源,以及PC寄存器的值。所以子进程不会运行执行过的fork函数以及fork函数以上的代码
    (ps:PC寄存器中存储着下一次该运行到哪一行代码)

  • 父子进程的虚拟地址空间不是同一块,但是由于子进程是从父进程拷贝过来的,所以fork完毕的一瞬间,父子进程的用户空间长得完全一致

  • 父子进程映射的物理地址不是同一块空间
    **写时拷贝:**当父子进程均不修改其中的内容的时候,此时映射的物理地址是同一块空间。若某个进程要修改其中的内容的时候,此时才会真正的申请一块新的物理地址空间给子进程的虚拟地址映射
    进程的介绍_第4张图片

getpid/getppid

  • 功能:获取进程号、获取父进程号
  • 原型: pid_t getpid(void);
    pid_t getppid(void);
    返回值:获取进程号、获取父进程号

_exit/exit

_exit

  • 功能:结束进程,销毁其在内存中的资源,且直接摧毁缓冲区,不会刷新缓冲区

  • 原型:void _exit(int status)

  • 参数:int status:可以传递进程推出状态值给其父进程,父进程可以通过wait/waitpid函数接收。可以传递任意整型
    exit

  • 功能:结束进程,销毁其在内存中的资源,会刷新缓冲区

  • 原型:void exit(int status)

  • 参数:int status:可以传递进程退出状态值给其父进程,父进程可以通过wait/ waitpid函数接收。可以传递任意整型

wait/waitpid

wait

  • 功能:阻塞函数,阻塞等待任意子进程退出;回收退出的子进程的资源

  • 原型:pid_t wait(int *wstatus)

  • 参数:int *wstatus:接收子进程传递回来的退出状态值,若不想接收,则填NULL

  • 返回值:>0,成功返回退出的子进程的PID号;= -1,函数运行失败,更新errno;
    若没有子进程,则wait函数运行失败;
    父进程只能回收子进程,无法回收孙子进程

  • 若子进程退出,父进程没有回收子进程的资源,此时子进程会变成僵尸进程。

  • 若没有子进程,则wait函数运行失败
    子进程退出状态
    wait(int *wstatus)中,int *wstatus指向的int类型参数,只有【8bit,15bit】用于存储子进程传递的退出状态值。返回为【0,255】
    所以子进程只能传递256种状态

  • 从wstatus中提取子进程退出状态值

int wstatus = -1;
pid_t wpid = wait(&wstatus);    
printf("wpid = %d  wstatus=%d\n", wpid, wstatus>>8); 
  • 判断子进程是否正常退出
    WIFEXITED(wstatus) 若正常退出,则返回真。
    正常退出:exit _exit 主调函数用return退出
    waitpid

  • 功能:阻塞等待指定子进程退出

  • 原型:pid_t waitpid(pid_t pid, int *wstatus, int options)

  • 参数:pid_t pid:
    < -1:阻塞等待指定进程组下的任意一个子进程退出
    -1:阻塞等待当前进程下的任意一个子进程退出;与wait函数的功能基本一致
    0:阻塞等待当前进程组下的任意一个子进程退出>0
    (>0:阻塞等待指定的子进程退出;子进程的pid号 == pid参数
    int *wstatus:接收子进程传递回来的退出状态值,若不想接收,则填NULL
    int options:0:阻塞方式运行,当指定的子进程没有退出的时候,该函数阻塞,直到指定子进程退出,解除阻塞;
    WNOHANG:非阻塞方式运行,当指定的子进程没有退出,该函数不阻塞,立即返回;

  • 返回值:成功,>0,成功回收到的子进程的pid号;=0,函数运行成功,但是此时子进程没有退出
    函数运行失败,返回-1,更新errno
    1.若指定的子进程不存在(没有子进程)的时候,函数运行失败;
    2.子进程无法回收父进程的资源;
    3.同级之间无法相互回收资源

Linux中的特殊进程

孤儿进程

父进程退出,子进程不退出,此时子进程被1号(int)进程收养,变成孤儿进程。孤儿进程会脱离终端控制,且运行在后端,不能用ctrl+c杀死后端进程,但是可以被kill -9杀死

#include 
#include 
#include 

int main(int argc, const char *argv[])
{
    //父进程退出 子进程不退出
    pid_t cpid = fork();
    if(cpid > 0)
    {
    }
    else if(0 == cpid)
    {
        while(1)                                                      
        {
            printf("this is child %d %d\n", getppid(), getpid());
            sleep(1);
        }
    }
    else
    {
        perror("fork");
        return -1;
    }

    return 0;
}

僵尸进程

子进程退出,父进程不退出,且父进程没有给子进程收尸,此时子进程就变成僵尸进程
注意:

  • 僵尸进程只能被回收,不能被杀死
  • 僵尸进程危害:占用进程号,占用部分内存空间,占用物理空间,占用进程调度块(PCB)等等…
  • 回收僵尸进程的方式:
    1>退出父进程后,子进程的资源由内核自动回收。
    2>wait/waitpid函数回收。缺点:阻塞函数,父进程无法做自己的事情;非阻塞形式,有可能收不到。
    3>结合信号的方式回收僵尸进程:当子进程退出后,通知父进程收尸
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
    //子进程退出,父进程不退出
    pid_t cpid = fork();
    if(0 == cpid)
    {
    }
    else if(cpid > 0)                                        
    {
        while(1)
        {
            printf("this is parent %d %d\n", getpid(), cpid);
            sleep(1);
        }
    }
    else
    {
        perror("fork");
        return -1;
    }

    return 0;
}

守护进程

又称为幽灵进程

  • 守护进程脱离于终端,且运行在后端
  • 守护进程在执行过程中不会将信息显示在任何终端上,避免影响前段任务执行。且不会被任何终端产生的终端信息所打断。
  • 守护进程的目的:需要周期性执行某个任务或者周期性等待处理某些事情的时候,为了避免影响前段执行或者被前端信息打断的时候,可以使用守护进程

守护进程的创建:

1>创建孤儿进程:所有工作都在子进程中执行,从形式上脱离终端控制
fork(),退出父进程
2>创建新的会话组:使子进程完全独立出来,防止兄弟进程对其有影响
setsid(),函数
3>修改当前孤儿进程的运行目录为不可卸载的文件系统:例如根目录,/tmp
防止运行目录被删除后,导致进程崩溃
4>重设文件权限掩码:umask(0),一般清零
5>关闭所有文件描述符,从父进程继承过来的文件秒数符不会用到,浪费资源

#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
    //创建孤儿进程
    pid_t cpid = fork();
    if(0 == cpid)
    {
        //创建新的会话
        pid_t sid = setsid();
        printf("sid = %d\n", sid);

        //修改运行目录为不可卸载的文件目录下
        chdir("/");

        //清空文件权限掩码
        umask(0);

        //关闭所有文件描述符
        for(int i = 0; i<getdtablesize(); i++)     
            close(i);

        while(1)
        {
            //守护进程运行的周期性代码
            sleep(1);
        }
    }
    return 0;
}

练习:

文件IO函数实现,拷贝文件。子进程先拷贝后半部分,父进程再拷贝前半部分。

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
    int fd_r = open("./1.png", O_RDONLY);
    if(fd_r < 0)
    {
        ERR_MSG("open");
        return -1;
    }

    int fd_w = open("copy.png", O_WRONLY|O_CREAT|O_TRUNC, 0644);
    if(fd_w < 0)
    {
        ERR_MSG("open");
        return -1;
    }
    //计算文件大小
    off_t size = lseek(fd_r, 0, SEEK_END);

    pid_t cpid = fork();
    if(cpid > 0)
    {
        sleep(4);

        //父进程拷贝前半部分
        //将偏移量修改到0
        lseek(fd_r, 0, SEEK_SET);
        lseek(fd_w, 0, SEEK_SET);

        char c = 0;
        for(int i=0; i<size/2; i++)
        {
            read(fd_r, &c, 1);
            write(fd_w, &c, 1);
        }

        printf("前半部分拷贝完毕\n");

    }
    else if(0 == cpid)
    {                                                           
        //子进程拷贝后半部分
        //将偏移量修改到size/2
        lseek(fd_r, size/2, SEEK_SET);
        lseek(fd_w, size/2, SEEK_SET);

        char c = 0;
        for(int i=size/2; i<size; i++)
        {
            read(fd_r, &c, 1);
            write(fd_w, &c, 1);
        }

        printf("后半部分拷贝完毕\n");

    }
    else
    {
        ERR_MSG("fork");
        return -1;
    }



    close(fd_r);
    close(fd_w);

    return 0;
}

打印时钟在终端上,若终端输入quit,结束时钟

#include 
#include 
#include 
#include 
#include 
int main(int argc, const char *argv[])
{
	pid_t cpid = fork();
	if(cpid > 0)
	{
		time_t t;
		struct tm *info = NULL;
	while(1)
	{	
		if(waitpid(-1,NULL,WNOHANG) > 0)
			break;
		t=time(NULL);
		info=localtime(&t);
		printf("%d-%02d-%02d  %02d:%02d:%02d\r",info->tm_year+1900,info->tm_mon+1,\
				info->tm_mday,info->tm_hour,info->tm_min,info->tm_sec);
		fflush(stdout);
		sleep(1);
	}
	}
	else if(cpid == 0)
	{
		char buf[128]="";
		while(1)
		{
			fgets(buf,sizeof(buf),stdin);
			buf[strlen(buf)-1]='\0';
			if(strcmp(buf,"quit") == 0)
				exit(0);
			sleep(1); 
		}
	}
	return 0;
}

你可能感兴趣的:(linux,开发语言,学习,算法)