Linux 进程

进程

进程和程序的区别

程序:
1.编译好的可执行的二进制文件
2.存放在磁盘上,指令和数据的有序集合(文件)
3.静态的,没有任何执行的概念

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

进程的特点

1.CPU调度进程时会给进程分配时间片(几毫秒~十几毫秒),当时间片用完后,cpu再进行其他进程的调度,实现进程的轮转,从而实现多任务的操作(什么是时间片?/ cpu是怎么调度进程的 / 时间片了解吗)

2.系统会为每一个进程分配0-4g的虚拟空间,0-3g(用户空间)是每个进程所独有的,3g-4g(内核空间)是所有进程共有的。

image-20250226184638518

进程的分段(linux)

  1. 正文段:也叫代码段,用于存放被执行的机器指令。(通常是只读的)
  2. 用户数据段:存放进程在执行时直接进行操作的所有数据,包括进程使用的全部变量在内。
  3. 系统数据段:有效地存放程序运行的环境,包括进程的控制信息等。

进程的类型

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

  • 批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。比如数据备份。

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

进程的运行状态

          D    uninterruptible sleep (usually IO)
               R    running or runnable (on run queue)
               S    interruptible sleep (waiting for an event to complete)
               T    stopped by job control signal
               t    stopped by debugger during the tracing
               W    paging (not valid since the 2.6.xx kernel)
               X    dead (should never be seen)
               Z    defunct ("zombie") process, terminated but not reaped by its parent
  1. 运行态(R):此时正在运行或者准备运行的进程。
  2. 睡眠态(等待态):可中断的睡眠态(S):处于等待状态中的进程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。
    不可中断的睡眠态(D):该状态的进程只能用wake_up()函数唤醒。
  3. 暂停态(T):进程被暂停或者终止
  4. 死亡态:进程结束 X
  5. 僵尸态(Z):当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生

进程状态转换图(重点)

image-20250226185646142

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

进程函数

创建进程fork

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

pid_t fork(void);
功能:创建子进程
返回值:
    成功:在父进程中:返回子进程的进程号 >0
         在子进程中:返回值为0
    失败:-1并设置errno
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    //创建子进程
    pid_t  pid = fork();
    if (pid < 0)
        perror("cerat process err");
        return -1;
    if (pid == 0)
        printf("in child\n");
    else
        printf("in parent\n");
    return 0;
}
  1. fork创建一个子进程,父进程返回子进程的pid,子进程中返回0。
  2. fork创建的子进程几乎拷贝了父进程所有的内容,fork之前的代码被复制并不会被执行,fork之后的代码被复制并执行。
  3. fork创建进程一旦成功,进程之间的空间就相会独立。各自分配0-4G的虚拟内存空间。
  4. fork创建进程之前打开的文件可以通过复制拿到同一个文件描述符 操作同一个文件(同一个文件指针)。
  5. 如果父进程退出,子进程没有退出,子进程会变成孤儿进程被init进程收养。变成后台进程。
  • init进程:系统初始化进程,系统启动先创建0号进程,由0号进程创建1号进程,创建完成后0号进程退出
  • 僵尸进程:子进程退出,父进程没有退出,父进程没有及时的对子进程的资源进行回收,子进程变成僵尸进程。避免僵尸进程的产生。

fork和vfork

1.fork(): 子进程拷贝父进程的数据段,代码段
   vfork(): 子进程与父进程共享数据段

2.fork(): 父子进程执行次序不确定
   vfork(): 保证子进程先运行,在调用exec()或exit()之前,与父进程数据共享,在exec()或exit()调用之后,父进程才能运行

总结:fork: 更通用,适用于需要创建一个完全独立的子进程的场景,vfork: 更适用于子进程立即执行 exec() 覆盖其自身的场景,因为它避免了不必要的地址空间复制,提高了性能。

waitpid和wait

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
功能:回收任一子进程的资源(阻塞)
参数:status:子进程退出状态,不接受子进程状态设为NULL
返回值:成功:回收的子进程的进程号
        失败:-1
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    // 创建子进程
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("cerat process err");
        return -1;
    }
    if (pid == 0)
    {
        sleep(5);
        printf("in child\n");   
    }
    else
    {
        printf("in parent\n");
        //阻塞等待子进程执行结束。
        wait(NULL);   
    }
    return 0;
}
pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程资源
参数:
    pid:>0     指定子进程进程号
         =-1   任意子进程
         =0    等待其组ID等于调用进程的组ID的任一子进程
         <-1   等待其组ID等于pid的绝对值的任一子进程
    status:子进程退出状态 NULL
    options:0:阻塞
    WNOHANG:非阻塞
返回值:正常:结束的子进程的进程号
      当使用选项WNOHANG且没有子进程结束时:0
      出错:-1

wait(NULL) = waitpid(-1,NULL,0) //阻塞回收任意子进程资源
waitpid(pid,NULL,0)                //阻塞回收指定进程
waitpid(-1,NULL,WNOHANG)          //不阻塞回收任意子进程

退出进程

#include <stdlib.h>

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

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

两者的区别
exit(0);
刷新缓存区
关闭所有的打开的文件指针。
_exit(0);
立即终止进程,不会刷新缓存区。

exit和return 的区别
exit:结束进程 return结束函数
exit函数   return 关键字

获取进程号

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

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

exec函数族

作用:在当前进程中执行一个新的进程

1. int execl(const char *path, const char *arg, ...
                       /* (char  *) NULL */);
使用可执行文件的绝对路径或相对路径 path 来替换当前进程的映像。参数通过变长参数列表传递,必须以 NULL 结尾。
2. int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
类似 execl,但 file 只需要是可执行文件的名称,函数会根据环境变量 PATH 查找可执行文件的路径。                       
3. int execle(const char *path, const char *arg, ...
                       /*, (char *) NULL, char * const envp[] */);
与 execl 类似,但允许传递一个自定义的环境变量列表 envp。这个环境会代替当前进程的环境变量。
4. int execv(const char *path, char *const argv[]);
使用路径 path 和参数数组 argv[] 替换当前进程。argv[0] 是程序名,argv[] 数组必须以 NULL 结束。
5. int execvp(const char *file, char *const argv[]);
类似 execv,但会根据 PATH 环境变量查找可执行文件。
6. int execvpe(const char *file, char *const argv[],char *const envp[]);
与 execvp 类似,但允许使用自定义的环境变量 envp。

守护进程

特点

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

守护进程(daemon)的作用:处理系统任务或提供服务,例如:web 服务器(如 httpd)、数据库服务(如 mysqld)、日志服务(如 syslogd)、计划任务服务(如 cron)等。

  1. 创建一个子进程,父进程退出(fork) 让子进程变成孤儿进程,成为后台进程
  2. 在子进程中创建一个新的会话(setsid)让子进程成为会话组的组长,为了让子进程完全脱离控制终端
  3. 改变运行路径为根目录(chdir)进程运行的路径不能被删除或卸载
  4. 重设文件权限掩码(umask)增大进程创建文件时的权限,提高灵活性
  5. 关闭文件描述符(close)将不需要的文件描述符关闭。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    // 创建子进程,父进程退出
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    if (pid == 0)
    {
        //子进程
        //创建会话组
        setsid();
        //修改运行路径
        chdir("/");
        //重设文件权限掩码
        umask(0);
        //关闭文件描述符
        for (int i = 0; i < 3; i++)
        {
            close(i);
        }
        //子进程不结束
        while (1);
    }
    else
    {
        //父进程退出
        exit(0);
    }
    return 0;
}
上一篇
下一篇