Linux - 进程

系统调用:

  1. 系统调用的概念:

    1. 系统调用是操作系统提供给用户程序的一组“特殊”函数接口(API 接口)
    2. 用户程序可以通过 API接口 获得操作系统(内核)提供的服务
  2. 系统调用的返回值:

    1. 系统调用 API接口 返回值有两种可能:
      • 负数 –> 错误
      • 0 –>成功
    2. 系统调用的错误信息存放在全局变量 errno 中,用户可用 perror 函数打印出错信息

文件 I/O:

  1. 文件描述符:文件描述符是非负整数,是文件的标识
  2. 打开现有文件或新建文件时,系统(内核)会返回一个文件描述符
  3. 具体操作是有四个函数:
    1. open() 函数:
      • 函数功能:打开或创建文件/设备,并返回用于后续操作的文件描述符
      • 函数原型:
        • int open(const char *pathname, int flags);
        • int open(const char *pathname, int flags, mode_t mode);
      • 函数头文件:
        • #include <sys/types.h>
        • #include <sys/stat.h>
        • #include <fcntl.h>
      • 函数参数:
        • pathname 打开文件的路径/名称
        • flags 文件打开方式
        • mode 此参数是否传递要看 flags 是否包含 O_CREAT
      • 函数返回值:
        • 正确情况:返回一个文件描述符(file descriptor)
        • 错误情况 :返回 -1
    2. close() 函数:
      • 函数功能:关闭一个当前进程的文件描述符
      • 函数原型: int close(int fd);
      • 函数头文件: #include <unistd.h>
      • 函数参数:要关闭的文件描述符(编号)
      • 函数返回值:0-关闭成功,1-关闭失败
    3. read() 函数:
      • 函数功能:从文件描述符 fd 指定的文件中读取 count 个字节数据,存储到 buf 指向的空间
      • 函数原型: ssize_t read(int fd, void *buf, size_t count);
      • 函数头文件: #include <unistd.h>
      • 函数参数:
        • fd 文件描述符
        • buf 万能指针,任意类型的空间(某个空间的首地址)
        • count 读取的字节数
      • 函数返回值: 0-关闭成功,1-关闭失败
    4. write() 函数:
      • 函数功能:向文件描述符 fd 指定的文件中写入从 buf 首地址开始的 count 个字节数据
      • 函数原型: ssize_t write(int fd, const void *buf, size_t count);
      • 函数头文件: #include <unistd.h>
      • 函数参数:
        • fd 文件描述符
        • buf 万能指针,任意类型的空间(某个空间的首地址)
        • count 写入的字节数
      • 函数返回值: 0-关闭成功,1-关闭失败

进程:

  1. 进程(Process) 是操作系统中,正在运行的程序实例

  2. 查询进程的 shell 指令 ps(常用指令 ps -aux

  3. 进程的状态:

    进程1

    进程2

  4. 进程的状态标识:

    进程3

  5. **ulimit 指令:**可以查询和设置系统的资源配置

  6. 进程控制块(PCB):

    1. Linux 通过控制 PCB 来管理进程
    2. OS 是根据 PCB 来对并发执行的进程进行控制和管理的
    3. 系统在创建一个进程的时候会开辟一段内存空间存放与此进程相关的 PCB 数据结构。PCB 是操作系统中最重要的记录型数据结构。 PCB 中记录了用于描述进程进展情况及控制进程运行所需的全部信息。 PCB 是进程存在的唯一标志,在 Linux 中 PCB 存放在 task_struct结构体中
    4. PCB 的工作职责与结构:
      1. 进程状态,将记录进程在等待,运行,或死锁:进程状态
      2. 调度信息, 由哪个调度函数调度,怎样调度等:资源
      3. 进程的通讯状况
      4. 因为要插入进程树,必须有联系父子兄弟的指针, 当然是 task_struct 型
      5. 时间信息, 比如计算好执行的时间, 以便 cpu 分配
      6. 标号,决定改进程归属
      7. 可以读写打开的一些文件信息
      8. 进程上下文和内核上下文
      9. 处理器上下文
      10. 内存信息
  7. 进程号:

    1. 每个进程都由一个进程号(PID)来标识,其类型为 pid_t

    2. 进程号的范围: 0~ /proc/sys/kernel/pid_max

    3. 0 号进程:通常也被称为 idle 进程或者 swapper 进程,是 linux 启动的第一个进程

    4. 1 号进程: 称为 init 进程,其中包含收养所有的孤儿进程

    5. 注意:进程号总是唯一的(在进程没有结束、被杀死之前),但进程号可以重用,当一个进程终止后,其进程号就可以再次使用了

    6. PID、PPID、PGID:

      • PID(进程 ID):进程的唯一标识符,由内核分配,每个运行的进程都有一个独特的 PID

      • PPID(父进程 ID):当前进程的父进程的 PID

      • PGID(进程组 ID):进程组的标识符,同一组的进程共享相同的 PGID

        进程4

    7. 查看进程号函数:

      1. getpid() 查看进程 ID
      2. getppid() 查看父进程 ID
      3. getpgid()查看进程组 ID
      4. 指令查看 PID 、 PPID、 PGID: ps -e -o pid,ppid,pgid,comm
  8. 进程的应用:

    1. 进程的创建 fork()、 vfork() :

      1. fork() 函数功能:创建一个新进程(本进程的子进程)
      2. fork() 函数返回值:
        1. fork() 函数有两个返回值,但是本质上还是一个返回值
        2. 成功:
          • 正整数 —在父进程(本进程)返回的是子进程的 PID
          • 0 —在子进程返回0
        3. 失败:返回 -1
      3. fork 函数运行:
        • 父进程与子进程运行的先后顺序不确定
        • 将父进程的地址空间的数据完全复制到子进程
      4. vfork() 函数:
        • vfork() 保证子进程先运行,在它调用 exec 或 exit 之后,父进程才可能被调度运行,在子进程中调用 exec 或 exit 之前, 子进程在父进程的地址空间中运行,也就是说 vfork 并不会重新开辟 4G 空间给子进程,父进程暂停运行,让子进程运行
    2. 进程的终止 _exit()、exit():

      1. 进程可以主动的终止当前进程的运行,并告诉 OS 可以释放本进程的 PCB
      2. _exit()函数 是一个系统调用 API,运行在内核态
      3. exit()函数 是一个标准库函数,运行在用户态
      4. 正常情况下返回 0 代表无错误,其他表示异常
      5. 参数范围最好是-128 ~ +127
    3. 回收进程资源 wait()、 waitpid():

      1. 子进程的返回状态存储在 wait、 waitpid 的参数 wstatus 中,但是 state 并不是子进程返回值,需要我们使用宏函数提取出来

      2. wait()函数:

        1. 函数原型:pid_t wait(int *wstatus);
        2. 函数功能:等待当前进程(父进程) 的子进程结束,并回收其子进程资源(阻塞型
        3. 函数参数:指针类型 提供一个缓冲区地址,存储的是子进程返回的状态
        4. 返回值:正数 成功,结束子进程的 PID,-1 失败(但是不会返回,此函数是阻塞型)
      3. waitpid()函数:

        1. 函数功能:等待指定的进程结束,并回收其进程资源

        2. 函数参数:

          1. 参数 1:等待进程的 PID

            进程5

          2. 参数 2:和 wait 的参数一样

            进程6

        3. 返回值:

          进程7

  9. 僵尸进程、孤儿进程、守护进程:

    1. 僵尸进程:
      • 如果一个进程已经终止,但是它的父进程尚未调用 wait() 或 waitpid() 对它进行清理,这时的进程状态称为僵死状态,处于僵死状态的进程称为僵尸进程(zombie process)。
      • 任何进程在刚终止时都是僵尸进程,正常情况下,僵尸进程都立刻被父进程清理了。
      • 在 Linux 系统中,僵尸进程(Zombie Process)是指已经完成执行(终止)但其父进程尚未收集其退出状态(exit status)的进程,僵尸进程本身已经不再执行任何任务,所有资源(如 CPU、内存等)都已释放,但它仍然在进程表中占据一个条目,直到父进程调用 wait()或 waitpid() 等系统调用来获取子进程的退出状态。
      • 养成习惯: fork 和 wait 要成对出现
    2. 孤儿进程:
      • 如果父进程退出而它的一个或多个子进程还在运行,那么这些子进程就被称为孤儿进程
      • 孤儿进程最终将被 init 进程 (进程号为 1 的 init 进程) 所收养并由 init 进程完成对它们的状态收集工作
      • 孤儿进程是没有危害的,孤儿进程是没有父进程的子进程,当孤儿进程没有父进程时,内核就会 init 设置为孤儿进程的父进程, init 进程就会调用 wait 去释放那些已经退出的子进程,当孤儿进程完成其声明周期之后, init 会释放掉其状态信息,孤儿进程实际上是不占用资源的,不会像僵尸进程那样占用 ID,损害运行系统
    3. 守护进程:
      • 守护进程也有人称呼为天使进程
      • Linux Daemon(守护进程)是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务, Linux 系统的大多数服务器就是通过守护进程实现的
      • 常见的守护进程包括系统日志进程 syslogd、 web 服务器 httpd、邮件服务器 sendmail和数据库服务器 mysqld 等
      • 守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源
      • 如果一个守护进程的父进程是 init 进程,则是因为它真正的父进程在 fork 出子进程后就先于子进程 exit 退出了,所以它是一个由 init 继承的孤儿进程
      • 守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备 stdout 还是标准出错设备 stderr 的输出都需要特殊处理
  10. 进程的替换:

    1. 每个进程都占用一个 PID,可能存在某个 PID 有特定的任务(比如 1 号进程),但是在某个时刻此进程的处理规则有细微的修改,或者需要将事件交给进程 B 处理,为了保证进程 B 拿到的 PID 为进程 A 的 PID,此过程称之为进程替换
    2. exec 内涵 exit 的功能
    3. exec 是一个函数族,里面有很多关于进程替换的函数(功能类似)
    4. exec 函数族与一般的函数不同,exec 函数族中的函数执行成功后不会返回,只有调用失败了,它们才会返回 -1,失败后从原程序的调用点接着往下执行

参考链接:

【Linux进程】Linux下的—七大进程状态(什么是进程状态?Linux下有哪些进程状态?)_linux进程状态详解-CSDN博客