4.【进程】

1. 概念

1.1. 程序:

  1. 编译好的可执行文件./a.out
  2. 存放在磁盘上的指令和数据的有序集合(文件)
  3. 静态的,没有执行的概念

1.2. 进程:

  1. 进程是程序的一次执行过程。
  2. 进程是动态的,包含创建、调度、执行、消亡。
  3. 进程是执行一个程序分配资源的总称。
  4. 独立的可调度的任务

2. 进程 特点

  1. 系统会为每一个进程分配0-4g的虚拟空间,0-3g(用户空间)是每个进程所独有的,3g-4g(内核空间)是所有进程共有的。
  2. CPU调度进程时 会给进程分配时间片(几毫秒~十几毫秒),当时间片用完后,cpu再进行其他进程的调度,实现进程的轮转,从而实现多任务的操作

3. 进程 段

Linux中的 进程包含三个段:

  • “数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
  • “正文段”存放的是程序中的代码
  • “堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量

4. 进程 分类

交互进程:

该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等

批处理进程:

该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。

守护进程:

该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。

5. 进程状态

  1. 运行态(TASK_RUNNING):R
    指正在被CPU运行或者就绪的状态。这样的进程被成为runnning进程。
  2. 睡眠态(等待态):
    ● 可中断睡眠态(TASK_INTERRUPTIBLE)S:
    ○ 处于等待状态中的进程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。
    ● 不可中断睡眠态(TASK_UNINTERRUPTIBLE)D:
    ○ 该状态的进程只能用wake_up()函数唤醒。
  3. 暂停态(TASK_STOPPED): T
    当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。
  4. 死亡态:
    进程结束 X
  5. 僵尸态(TASK_ZOMBIE):Z
    当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生

6. 进程状态转换图

进程创建后,进程进入就绪态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作(阻塞结束)后又可进入就绪态,等待CPU的调度,当进程运行结束即进入结束态

7. 函数实现

fork exit _exit wait waipid getpid getppid

7.1. 创建进程 fork

pid_t fork(void);
功能:创建子进程
返回值:
    成功:
    	在父进程中:返回子进程的进程号 >0
        在子进程中:返回值为0
    失败:
    	-1并设置errno

7.1.1. 特点:

  1. 子进程几乎拷贝了父进程的全部内容。
    1. 包括代码、数据、系统数据段中的pc值、 栈中的数据、父进程中打开的文件等;
    2. 但它们的PID、PPID是不同的
  1. 父子进程 有独立的地址空间互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响。
  2. 若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程
  1. 若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程产生)
  1. fork函数之前的代码会被复制,但是不会被再执行一遍,fork之后的代码会被复制,并且父子进程分别执行一遍。
  1. fork之前打开的文件,fork之后拿到的文件描述符,操作的是同一个文件指针。

(不同的进程打开相同的文件,操作的是不同的文件指针,虽然是指向同个文件,但是各操作各的)

  1. fork函数创建父子进程执行顺序不一定
    1. vfork:先执行完子进程,再执行父进程

7.2. 回收进程 wait/waitpid

pid_t wait(int *status);
功能:回收 任一 已退出的子进程的资源  (阻塞)(回收僵尸进程的资源)
参数:
	status:子进程退出状态,不接受子进程状态设为NULL
返回值:
	成功:回收的子进程的进程号
    失败:-1
pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程资源
参数:
    pid:>0    等待进程号为pid的子进程。
         =-1   等待任一个子进程。与wait等效
         =0    等待 进程组号 与 目前进程组号 相同 的任何子进程,
         	   也就是说 和 任何 调用waitpid函数的进程 在同一个进程组的子进程
         <-1   等待 进程组号 为 pid绝对值 的任何子进程
         
    status:子进程退出状态
    
    options:
    	0:阻塞,即wait
        WNOHANG:非阻塞:
        	如果 pid指定的子进程没有结束,则waitpid函数立即返回0,而不是阻塞在这个函数上等待;
           	如果 已经结束了,则返回该子进程的进程号。
        WUNTRACED:如果子进程进入暂停状态,则马上返回
        
返回值:
	正常:
 		结束的 子进程的 进程号
    	当使用选项WNOHANG且没有子进程结束时:0	并且父进程不等待
    出错:
    	-1,并且将失败的原因存放在errno变量中
wait(NULL);                 //不关心状态,阻塞等待任一子进程退出
waitpid(-1, NULL, 0);       //和wait等价;不关心状态,阻塞回收任一子进程

waitpid(pid, NULL, 0);      //不关心状态,阻塞回收指定pid的子进程
waitpid(-1, NULL, WNOHANG); //不关心状态,不阻塞 试探回收 任意子进程

7.3. 退出进程 exit _exit

void exit(int status);
功能:结束进程,刷新缓存
参数:退出的状态

void _exit(int status);
功能:结束进程,不刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
     通常0表示正常结束;
     其他的数值表示出现了错误,进程非正常结束

return 与exit的区别:

  • return:关键字,当子函数中有return时返回到函数调用位置,并不结束进程(函数的退出)
  • exit:函数,不管在子函数还是主函数,都可以结束进程(进程的退出)

GPT回答 子进程回收问题(重要)

在Linux的C语言中,子进程终止后,它占用的一部分资源会被系统回收。这包括子进程的内存空间、打开的文件描述符、进程状态等。

系统会自动回收子进程的资源,但它不会自动清理子进程的终止状态。为了避免僵尸进程的出现,父进程通常需要调用wait()、waitpid()或类似的系统调用来等待子进程的终止并回收子进程的终止状态。

父进程在调用这些等待函数时,会被阻塞,直到其中一个子进程终止。当父进程成功回收该子进程的终止状态后,子进程会被完全销毁,释放所有资源,并且父进程可以获取到子进程的终止状态。

如果父进程没有等待子进程的终止,或者一直没有调用相应的回收函数,那么子进程就会一直处于僵尸状态,占用系统的进程表项资源,直到父进程退出或显式地回收子进程。

所以,尽管操作系统会在子进程终止时回收一些资源,但父进程仍然需要调用相应的函数来回收子进程的终止状态,以避免僵尸进程的产生。

GPT:子进程main的return问题

在Linux C编程中,子进程的main函数不会直接返回给任何特定的实体。(简单说,无所谓)

7.4. 获取进程号 getpid/getppid

pid_t getpid(void);
功能:获取当前进程的进程号

pid_t getppid(void);
功能:获取当前进程的父进程号
  • getpid 获取 当前进程的 进程号
  • getppid 获取 当前进程的 父进程的 进程号
  • 在父进程中,fork返回值 即子进程的进程号

8. 守护进程

8.1. 特点

  1. 守护进程是后台进程;
  2. 生命周期比较长,从系统启动时开启,系统关闭时结束;
  3. 它是脱离控制终端且周期执行的进程。

8.2. 步骤

  1. 创建子进程,父进程退出

让子进程变成孤儿进程,成为后台进程;fork()

  1. 在子进程中创建新会话

让子进程成为会话组组长,为了让子进程完全脱离终端;setsid()

  1. 改变进程运行路径为根目录

原因:进程运行的路径不能被删除或卸载;chdir("/")

  1. 重设文件权限掩码

目的:增大进程创建文件时权限,提高灵活性;umask(0)

  1. 关闭文件描述符

将不需要的文件关闭;close()

创建流程:

代码练习:

创建一个守护进程,循环间隔1s向文件中写入一串字符“hello”

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

int main(int argc, char *argv[])
{
    // 创建子进程,父进程退出
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0) //子进程 作为守护进程
    {
        setsid();   //在子进程中创建新会话,让子进程作为会话组组长
        chdir("/"); //改变运行路径为根目录
        umask(0);   //重设文件权限掩码 (保证权限最大,都和1&)
        close(0);   //关闭用不到的文件描述符
        close(1);

        // 业务逻辑
        int fd = open("/tmp/info.log", O_CREAT | O_TRUNC | O_WRONLY, 0777); //这个权限最大,因为umask为0
        if (fd < 0)
        {
            perror("open err");
            return -1;
        }

        while (1)
        {
            write(fd, "hello\n", 6);
            sleep(1);
        }
    }
    else //父进程退出。由init系统回收资源
        exit(0);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值