Loading

进程基础

个人学习随笔,仅做学习之用

【1】进程的概念

1. 什么是进程

  1. 进程是一个程序的一次执行过程
  2. 进程是一个独立的,可以调度的任务
  3. 进程在被调度的时候,系统会分配和释放各种资源(cpu资源,进程调度块,内存资源)
  4. 进程是一个抽象的概念。

2. 进程和程序的区别

​ 程序是静态的,它存储在磁盘上的可执行二进制文件

​ 进程是动态的概念,它是有程序的一次执行过程,包括进程的创建、调度、消亡,是存在内存中

3. 进程的内存管理

  1. 每个进程都会分配4G的内存空间(虚拟的空间)
  2. 其中0-3G是用户空间代码使用,3G-4G是内核空间使用过的。
  3. 3G-4G的内核空间是所有进程共享的。

虚拟地址和物理地址

​ 物理地址:内存条(硬件上)真正存在的存储空间

​ 虚拟地址:程序运行后,会有4G虚拟地址,由物理地址映射到虚拟地址上的。

​ 在32位操作系统中,虚拟空间是4G

​ 在64位操作系统中,2^48 = 256TB

image

虚拟内存的目的:cpu能够动态分配内存

每个进程都有自己独立的段

1)数据段 (全局变量,静态变量,堆)

2)正文段(存放代码)

3)栈(局部变量,形参,返回值)

每个进程都有自己的虚拟空间,那么进程之间的通信需要使用 3~4G的内存空间。

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

  1. 内存的申请和释放

  2. 进程的文件描述符分配:1024个

  3. 分配cpu时间片

  4. 管理自己的虚拟地址空间,在使用的时候会映射到物理内存上。

    ... ...

5. 进程号

操作系统会给每一个进程分配一个id号,这个id号就是进程号(PID)

1.主要的进程标识

​ 进程号:PID (process id)

​ 父进程号:PPID(parent process id)

2.PID是进程的唯一标识

3.操作系统刚启动的时候,会启动三个进程

​ PID:0 idle进程 (操作系统引导程序,创建1号,2号进程) 唯一一个没有调用函数创建的进程.

​ PID:1 init进程 初始化内核的各个模块,当内核启动完成后,用于收养孤儿进程(没有父进程的进程)

​ PID:2 kthreadd 用于进程间调度。

【2】查看进程

1. 查看进程的shell命令

1)$ ps -aux

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

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND

2)$ ps -ajx $ ps -ef

PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND

父进程 进程号 进程组id 会话id 进程状态

进程组:若干个进程的集合,为进程组。默认情况下,新创建的进程会继承父进程的组id

会话组:若干个进程组的集合,称之为会话。

STAT

$ man ps /stat

               D    不能被中断的阻塞态
               R   	正在运行的进程
               S    休眠状态的进程
               T    被挂起/被追踪的进程
               X    死亡态,死亡是一瞬间的,不能被捕获
               Z    僵尸进程


               <    高优先级
               N    低优先级
               L    有些页被锁紧内存中
               s    会话组长,有子进程的进程
               l    多线程
               +    运行在前台的进程

3)$ pidof

功能:根据程序名获取PID号

$ pidof /bin/bash

4)$ pstree

5)$top

功能:实时显示进程状态。

$ top -d 秒数 设置刷新秒数

​ $ top -d 2

$ top -p pid 显示指定进程的状态

6)$kill

功能:杀死进程

kill -9 pid 根据pid杀死进程

killall -9 进程名字 根据指定的名字,杀死进程

$ killall -9 a.out

2. 进程的状态

  1. 创建态
  2. 就绪态
  3. 运行态
  4. 阻塞态
  5. 终止态

image

【3】进程相关的系统调用

man 2 卷

1)fork

功能:创建一个子进程;
头文件:
       #include <unistd.h>
原型:
       pid_t fork(void);
返回值:
    成功: >0 	在父进程中返回,创建的子进程的PID
    	   0  在子进程中,返回0;
	失败,	-1 	在父进程中,返回-1,没有子进程被创建,更新errno;

注意:

  1. 父进程会拷贝一份资源给子进程(克隆),父子进程的资源是一致的,但是子进程不会运行创建它的那个fork函数以及fork以上的代码。

  2. 子进程会拷贝父进程的文件描述符。

  3. 子进程会拷贝父进程的虚拟地址空间;

    子进程会拷贝父进程的数据段,正文段,栈段

    父子进程堆栈区的虚拟地址完全一致。

    但是物理地址不一致。

image

2)getpid getppid

功能:获取当前进程/父进程的PID;
头文件:
       #include <sys/types.h>
       #include <unistd.h>
原型:
       pid_t getpid(void);
       pid_t getppid(void);
返回值:
    获取当前进程/父进程的PID
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc, const char *argv[])
{
    printf("this is fork function\n");
    char c = 10; 

    pid_t pid = fork();

    if(pid > 0)
    {   
        sleep(1);
        printf("this is parent %d %d\n", getpid(), pid);
        printf("pid = %d %d\n", pid, __LINE__);
        printf("parent c=%d %p\n", c, &c);
    }   
    else if(0 == pid)
    {                                                                   
        printf("this is child %d %d\n", getppid(), getpid());
        printf("pid = %d %d\n", pid, __LINE__);
        printf("child c=%d %p\n", c, &c);

        c = 20; 
        printf("child c=%d %p\n", c, &c);

    }   
    else if(pid < 0)
    {   
        perror("fork");
        return -1; 
    }   

    while(1)
        sleep(1);
    return 0;
}

3)_exit / exit

i)_exit

man 2 卷

功能:终止进程,清楚进程使用的内存空间,销毁其在内核中的各种数据,不会刷新缓冲区;
头文件:
       #include <unistd.h>
原型:
       void _exit(int status);
参数:
    int status:可以利用这个参数传递进程终止的状态,可以输入任意整形。
    			这个参数会被 	wait/waitpid函数。    

注意:_exit函数会宗旨进程,不会刷新缓冲区,直接销毁缓冲区;

ii)exit

​ man 3 卷

功能:终止进程,并刷新缓冲区;
头文件:
       #include <stdlib.h>
原型:
       void exit(int status);
参数:
    int status:可以利用这个参数传递进程终止的状态,可以输入任意整形。
    			这个参数会被 	wait/waitpid函数。    

4)wait/waitpid

i)wait
功能:阻塞函数,等待子进程退出,并回收子进程的资源;
头文件:
       #include <sys/types.h>
       #include <sys/wait.h>
原型:
       pid_t wait(int *status);
参数:
    int *status:获取子进程退出的状态;接收 exit/_exit中的status产生书。
    			如果不想接收,填NULL;
返回值:
    成功,返回退出的子进程的pid;
   	失败,返回-1,更新errno;

1.阻塞等待子进程退出,并回收子进程的资源;
2.如果没有子进程,父进程不等待,直接退出。
ii)waitpid
功能:等待指定进程退出,并回收指定进程的资源;
头文件:
       #include <sys/types.h>
       #include <sys/wait.h>
原型:
      pid_t waitpid(pid_t pid, int *status, int options);
参数:
    pid_t pid:
       < -1   回收指定进程组下的任意子进程,指定进程组的id = pid的绝对值。例如 PGID=1976,则pid填-1976
       -1     等待当前进程下的任意子进程。
       0      回收当前进程组下的任意子进程
       > 0    回收指定进程。

    int *status:获取子进程退出的状态;接收 exit/_exit中的status产生书。
    			如果不想接收,填NULL;
	
	int options:
        	0,阻塞等待,如果指定的进程没有退出,则当前进程阻塞等待。
        	WNOHANG:非阻塞,就算指定进程没有退出,当前进程不等待,继续执行后面的代码;
返回值:
    成功,阻塞,返回被回收进程的pid.
    	非阻塞,如果指定进程退出,则返回被回收的进程的pid;
    			如果指定进程没有退出,则返回0;
	失败,返回-1,更新errno;    			

从wait(&s)提取到exit(status)中status的值 ----用宏函数的方式提取

1)WEXITSTATUS(status):提取子进程终止的状态值;

​ int ret = WEXITSTATUS(status);

2)WIFEXITED(status):判断子进程是否正常退出。

​ 返回1,代表子进程正常退出;

​ 返回0,代表子进程异常退出。

判断进程是否正常退出
调用函数或return退出的都是正常退出,
由于信号退出的是异常退出,例如kill -9 pid

pid_t wait(int*s) 中s存储的是一个32位的整形,其中[8,15]总共8bit存放的是exit(status)中的status;

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

void child_func();
int main(int argc, const char *argv[])
{
    pid_t pid = fork();

    if(pid > 0)
    {
        //父进程
        printf("parent.....\n");
        sleep(1);

        int status;
        pid_t c_pid = wait(&status);
        printf("回收子进程资源成功 %d\n", c_pid);

        printf("status=%d\n", status);
        int ret = WEXITSTATUS(status);
        printf("ret = %d\n", ret);

        //判断进程是否正常退出
        //调用函数或return退出的都是正常退出,
        //由于信号退出的是异常退出,例如kill -9 pid
        int res = WIFEXITED(status);
        if(res)
        {
            printf("正常退出\n");
        }
        else
        {
            printf("异常退出\n");
        }

        while(1)
            sleep(1);
    }
    else if(0 == pid)
    {
        //子进程
        child_func();
        printf("child end\n");
    }
    else
    {
        perror("fork");
        return -1;
    }

    printf("准备退出了\n");

    return 0;
}

void child_func()
{
    int i=0;
    while(1)
    {
        printf("child....\n");
        sleep(1);
        i++;
        if(i==3)
        {
        //  exit(10);                                       
        }
    }
}

【4】linux的特殊进程

1. 孤儿进程

没有父进程的进程,父进程退出,子进程没有退出。子进程会被init进程收养,脱离终端控制。

孤儿进程不能用ctrl+c退出,但是可以被kill -9杀死

ctrl + alt + f1 	切换到字符终端 (f1-f6)
ctrl + alt + f7
#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    pid_t pid = fork();
    if(pid>0)
    {   
    }   
    else if(0 == pid)
    {                                                                                                                                                          
        while(1)
        {
            printf("child....\n");
            sleep(1);
        }
    }   
    else 
    {   
        perror("fork");
        return -1; 
    }   

    return 0;
}

2. 僵尸进程

子进程退出,父进程没有退出,且子进程的资源没有被回收。这时候子进程就是僵尸进程。

注意:

​ 1.僵尸进程只能被回收,不能被kill;

​ 2.父进程退出后,子进程的资源将由内核回收。

危害:

​ 1.占用进程号

​ 2.占用内存空间,占用物理空间,以及进程控制块等等......

如何回收僵尸进程:

​ 1.wait/waitpid函数回收。缺点:阻塞函数,父进程不能做自己的事情。

​ 2.用信号的方式回收。

#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    pid_t pid = fork();

    if(pid > 0)
    {
        while(1)
        {   
            printf("parent....\n");
            sleep(1);
            wait(NULL);
        }   
    }
    else if(0 == pid)                                                              
    {   
    }
    else
    {
        perror("fork");
        return -1; 
    }
    return 0;
}

3. 守护进程

又称之为幽灵进程。

1)守护进程常常是在系统启动的时候开始运行,在系统关闭的时候终止

2)独立于控制终端,且周期性执行某个任务或等待处理某些事情

3)大多数服务器都是由守护进程实现的。

守护进程的创建

1)创建孤儿进程

​ fork()创建子进程,父进程退出

2)创建新的会话

​ 子进程创建新的会话,就不再依附于父进程的会话

setsid函数

       #include <unistd.h>
原型:
       pid_t setsid(void);
成功,返回新的会话id;
失败,返回-1,更新errno;

1.创建新的会话,成为会话组组长;
2.创建新的进程组,成为进程组组长;
3.脱离父进程的控制。

3)修改当前孤儿进程的运行目录为根目录

chdir函数

       #include <unistd.h>
       int chdir(const char *path);
参数:
    char *path:指定修改后的运行目录 	"/";
返回值:
    成功,返回0;
	失败,返回-1;

注意:从这一步往后的程序,当前进程运行在根目录下

4)重设文件权限掩码

守护进程对文件进行权限设置,一般保留文件原有权限。

umask(0);

5)关闭所有文件描述符

子进程拷贝了父进程的文件描述符,需要将所有文件文件描述符关闭。包括0,1,2

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
    //创建孤儿进程
    pid_t pid = fork();

    if(pid > 0)
    {
    }
    else if(pid == 0)
    {
        //子进程
        //创建新的会话
        pid_t sid = setsid();
        printf("sid = %d\n", sid);

        int fd = open("./1.png", O_RDONLY);
        if(fd < 0)
        {
            perror("open");
            return -1;
        }
        //修改运行目录
        chdir("/");

        //修改文件权限掩码                                                                                                           
        umask(0);

        //关闭文件描述符
        int i = 0;
        for(i=0; i<1024; i++)
        {
            close(i);
        }


        while(1)
        {
            //程序功能
            sleep(1);
        }
    }
    else
    {
        perror("fork");
        return -1;
    }

    return 0;
}

【5】exec函数族

功能:

​ 1)执行一个可执行程序,替代当前进程。

​ 2)调用函数族后,没有生产新的进程,而是替换了原有的进程正文

​ 3)调用前后,进程的pid不变,但是执行的是调用后的进程代码。

       #include <unistd.h>

       extern char **environ;

       int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[], char *const envp[]);
posted @ 2021-06-03 10:57  Yangtai  阅读(287)  评论(0编辑  收藏  举报