当前位置: 首页 > news >正文

再谈Linux多进程——进程处理与守护进程

进程是操作系统资源分配的基本单位,进程是程序执行的实例!!!

Linux 内核中管理进程关键数据结构(进程控制块:PCB)

struct task_struct
{volatile long state; //说明了该进程是否可以执行,还是可中断等信息unsigned long flags; // flags 是进程号,在调用fork()时给出int sigpending; // 进程上是否有待处理的信号mm_segment_t addr_limit;  //进程地址空间,区分内核进程与普通进程在内存存放的位置不同  //0-0xBFFFFFFF for user-thead    //0-0xFFFFFFFF for kernel-thread//调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度volatile long need_resched;int lock_depth;    //锁深度long nice;       //进程的基本时间片//进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHERunsigned long policy;struct mm_struct *mm;    //进程内存管理信息 (内存管理结构)int processor;//若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新unsigned long cpus_runnable, cpus_allowed;struct list_head run_list;   //指向运行队列的指针unsigned long sleep_time;   //进程的睡眠时间//用于将系统中所有的进程连成一个双向循环链表, 其根是init_taskstruct task_struct *next_task, *prev_task;struct mm_struct *active_mm;struct list_head local_pages;      //指向本地页面      unsigned int allocation_order, nr_local_pages;struct linux_binfmt *binfmt;      //进程所运行的可执行文件的格式int exit_code, exit_signal;int pdeath_signal;           //父进程终止时向子进程发送的信号unsigned long personality;//Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序int did_exec:1; pid_t pid;          //进程标识符,用来代表一个进程pid_t pgrp;        //进程组标识,表示进程所属的进程组pid_t tty_old_pgrp;      //进程控制终端所在的组标识pid_t session;             //进程的会话标识pid_t tgid;int leader;    //表示进程是否为会话主管struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;struct list_head thread_group;          //线程链表struct task_struct *pidhash_next;    //用于将进程链入HASH表struct task_struct **pidhash_pprev;wait_queue_head_t wait_chldexit;      //供wait4()使用struct completion *vfork_done;         //供vfork() 使用unsigned long rt_priority;       //实时优先级,用它计算实时进程调度时的weight值//it_real_value,it_real_incr用于REAL定时器,单位为jiffies, 系统根据it_real_value//设置定时器的第一个终止时间. 在定时器到期时,向进程发送SIGALRM信号,同时根据//it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。//当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送//信号SIGPROF,并根据it_prof_incr重置时间.//it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种//状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据//it_virt_incr重置初值。unsigned long it_real_value, it_prof_value, it_virt_value;unsigned long it_real_incr, it_prof_incr, it_virt_value;struct timer_list real_timer;        //指向实时定时器的指针struct tms times;                      //记录进程消耗的时间unsigned long start_time;          //进程创建的时间//记录进程在每个CPU上所消耗的用户态时间和核心态时间long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; //内存缺页和交换信息://min_flt, maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换//设备读入的页面数); nswap记录进程累计换出的页面数,即写到交换设备上的页面数。//cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。//在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;int swappable:1; //表示进程的虚拟地址空间是否允许换出//进程认证信息//uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid//euid,egid为有效uid,gid//fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件//系统的访问权限时使用他们。//suid,sgid为备份uid,giduid_t uid,euid,suid,fsuid;gid_t gid,egid,sgid,fsgid;int ngroups;                  //记录进程在多少个用户组中gid_t groups[NGROUPS];      //记录进程所在的组//进程的权能,分别是有效位集合,继承位集合,允许位集合kernel_cap_t cap_effective, cap_inheritable, cap_permitted;int keep_capabilities:1;struct user_struct *user;struct rlimit rlim[RLIM_NLIMITS];    //与进程相关的资源限制信息unsigned short used_math;         //是否使用FPUchar comm[16];                      //进程正在运行的可执行文件名int link_count, total_link_ count;  //文件系统信息//NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空struct tty_struct *tty;unsigned int locks;//进程间通信信息struct sem_undo *semundo;       //进程在信号灯上的所有undo操作struct sem_queue *semsleeping;   //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作//进程的CPU状态,切换时,要保存到停止进程的task_struct中struct thread_struct thread;struct fs_struct *fs;           //文件系统信息struct files_struct *files;    //打开文件信息  (打开文件表)spinlock_t sigmask_lock;   //信号处理函数struct signal_struct *sig;   //信号处理函数sigset_t blocked;                //进程当前要阻塞的信号,每个信号对应一位struct sigpending pending;      //进程上是否有待处理的信号unsigned long sas_ss_sp;size_t sas_ss_size;int (*notifier)(void *priv);void *notifier_data;sigset_t *notifier_mask;u32 parent_exec_id;u32 self_exec_id;spinlock_t alloc_lock;void *journal_info;
}

通过命令行直接运行程序或者使用系统的启动脚本自动启动程序,表象上的一种创建方式,其底层本质上还是通过fork、exec去创建进程。

# 1. 命令行直接运行 
./program 
# 或者指定完整路径 
/usr/bin/program 
# 2. 后台运行 
nohup ./program 2<&1 > /dev/null & 
# 3. 通过脚本启动 
bash start_script.sh

fork()函数

完整复制父进程的地址空间(采用写时复制COW技术),子进程继承父进程的文件描述符、信号处理等资源,子进程获得新的PID和独立的内存空间。使用场景: •  需要与父进程共享环境的子进程  •  并行任务处理

(COW:copy-on-write)是一种内存管理技术,将复制操作推迟到第一次写入时进行:在创建一个新副本时,不会立即复制资源,而是共享原始副本的资源;当修改时再执行复制操作。该技术最初产生于Unix系统,用于实现一种傻瓜式的进程创建,最早的fork会将调用进程的所有内容原封不动的拷贝到新产生的子进程中去,这些拷贝的动作很消耗时间,而如果fork完之后我们马上就调用exec,这些辛辛苦苦拷贝来的东西又会被立刻抹掉,所以为了效率,现在的fork结束后并不立刻复制父进程的内容,而是到了真正实用的时候才复制,这样如果下一条语句是exec,它就不会白白作无用功了,也就提高了效率。这是实现原理。掌握这种理念,可以应用在自己的程序设计开发中。

#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "sys/wait.h"/*fork() 通过复制调用进程来创建一个新进程。新进程称为子进程。调用进程称为父进程。 RETURN VALUE     成功返回pid号,其中-1表示失败,0表示是子进程。*/int main() {pid_t pid = fork();if (pid < 0) {perror("fork");exit(1);}if (pid == 0) {printf("child process: pid = %d, ppid = %d\n", getpid(), getppid());sleep(5);} else {printf("parent process: pid = %d, ppid = %d\n", getpid(), getppid());//等待子进程结束,也可以用wait,waitpid可以指定等待的子进程,而wait是等待任意一个子进程结束waitpid(pid, NULL, 0);//wait(NULL);}return 0;
}

exec函数族

完全替换当前进程的代码段,并保持进程ID不变,有多个变体处理不同参数传递方式。使用场景: •  执行外部程序  •  改变当前进程映像  •  配合fork()实现"fork-exec"模式

/*path:要执行的程序的路径。arg:程序的参数,第一个参数通常是程序名,后面是程序需要的参数列表,最后必须以(char *)NULL结束*/
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
execl("/path/to/prog", "prog", "arg1", NULL);  // 参数列表/*path:要执行的程序的路径。argv:是一个字符串数组,表示程序的参数列表,第一个元素通常是程序名,最后一个元素必须是NULL*/
int execv(const char *path, char *const argv[]);
execv("/path/to/prog", argv);                 // 参数数组char *argv[] = { "ls", "-l", NULL };
execv("/bin/ls", argv);/*path:要执行的程序的路径。参数列表以(char *)NULL结束,然后传入环境变量数组envp*/
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
char *envp[] = { "HOME=/usr/home", "LOGNAME=home", NULL };
execle("/bin/ls", "ls", "-l", NULL, envp);execle("/path/to/prog", "prog", NULL, envp);  // 自定义环境

用例 (注意:exec成功后不会返回!!!)

#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "sys/wait.h"int main() {pid_t pid = fork();if (pid < 0) {// 错误处理perror("fork failed");exit(1);} else if (pid == 0) {// 子进程execl("/bin/ls", "ls", "-l", NULL);// 如果exec执行成功,不会执行到这里// 如果执行到这里,说明exec失败perror("exec failed");exit(1);} else {// 父进程wait(NULL); // 等待子进程结束}return 0;
}

vfork()函数

轻量级进程创建方式,其子进程共享父进程地址空间,子进程优先运行,父进程阻塞,子进程必须立即调用exec()或_exit();在内存受限系统需要立即执行新程序的场景中使用。

#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "sys/wait.h"int main() {pid_t pid = vfork();if (pid == 0) {execl("/bin/ls", "ls", "-l", NULL);_exit(127); // 仅exec失败时执行} else if (pid > 0) {int status;waitpid(pid, &status, 0);}return 0;
}

一般场景: 使用 fork() - 最通用、最安全的选择

执行新程序: 使用 fork() + exec() 组合

避免的做法: 不要在 vfork() 子进程中执行复杂操作

特殊需求: 需要细粒度控制资源共享时使用 clone()  

进程分离

进程分离是指一个进程能够脱离其父进程,即使父进程终止后仍能继续运行。当子进程与其父进程分离时,它会成为一个后台进程或守护进程,能够独立继续运行而不受父进程生命周期的影响。

#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "sys/wait.h"int main() {pid_t pid = fork();if (pid < 0) {perror("fork");exit(1);}if (pid == 0) {setsid();//创建一个新的会话和进程组,子进程分离sleep(10);printf("child process: pid = %d, ppid = %d\n", getpid(), getppid());} else {printf("parent process: pid = %d, ppid = %d\n", getpid(), getppid());}return 0;
}

守护进程

守护进程是在后台运行的一种特殊进程,与终端或用户会话无关。它们通常在系统启动时启动,并持续运行直到系统关闭。规范的守护进程创建过程中需要进行两次 fork,这是防止守护进程受到终端相关的信号影响,这种方式虽然看起来有点复杂,但是能够最大程度地确保守护进程的独立性和稳定性。

#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "sys/stat.h"
#include "sys/wait.h"
#include "fcntl.h"int main() {// 第一次 forkpid_t pid = fork();if (pid < 0) {exit(1);}if (pid > 0) {// 父进程退出exit(0);}// 创建新会话if (setsid() < 0) {exit(1);}// 第二次 forkpid = fork();if (pid < 0) {exit(1);}if (pid > 0) {// 第一子进程退出exit(0);}//第二次 fork 后不需要调用 setsidchdir("/");umask(0);// 关闭0、1、2文件描述符close(0);close(1);close(2);// 将标准输入、输出和错误重定向到 /dev/nullopen("/dev/null", O_RDWR);dup(0);dup(0);printf("child process: pid = %d, ppid = %d\n", getpid(), getppid());// 在此处添加你的守护进程任务代码return 0;
}

僵尸进程

子进程死亡时父进程没有回收,这样就造成子进程资源无法回收,通常用 ps 可以看到它被显示为defunct,这样就产生了僵尸进程。它将永远保持这样直到父进程回收。

避免僵尸进程手段

父进程主动回收子进程(wait/waitpid);

父进程注册SIGCHLD信号,在回调中进程回收;  

父进程忽略SIGCHLD、SIGCLD信号,子进程结束后由内核回收;  

fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。

// 方法1: 父进程调用wait/waitpid 
while (waitpid(-1, NULL, WNOHANG) > 0); //方法2: 父进程注册SIGCHLD信号,在回调中进程回收;
sigaction(SIGCHLD, &sigaction_fd, NULL); // 方法3: 忽略SIGCHLD信号 
signal(SIGCHLD, SIG_IGN);// 方法4: 双fork技术 
if (fork() == 0) {     if (fork() == 0) {         // 实际工作进程     }     exit(0); // 中间进程立即退出 
} 
wait(NULL); // 父进程回收中间进程

进程间通信选择

场景推荐IPC原因
大数据传输共享内存零拷贝,速度最快
结构化消息消息队列自带消息类型和优先级
简单数据流管道/命名管道使用简单
跨主机通信Socket(套接字)网络透明
异步事件通知信号轻量级

进程控制是庞大的知识脉络,是系统编程和进程管理的重点,未来也将持续逐部分浅谈与分享!!!

http://www.dtcms.com/a/438983.html

相关文章:

  • 广东平台网站建设制作大型网站只做要多少钱
  • 【LLM】基于ms-Swift大模型SFT和RL训练
  • 基于VisionMaster实现数据实时存储至MySQL
  • 文化共享工程网站建设情况做外贸比较好的网站
  • 淮安建设工程协会网站查询建立网上商城应考虑哪些问题
  • 五华网站建设 优帮云wordpress用户私信功能
  • MVC架构模式与三层架构的关系详解
  • 建设网站个人简介范文下载安装wordpress 主题
  • Swift 算法剖析:如何保证分块解析 Data 不发生数据割裂?(下)
  • 基于ArcGIS实现Shapefile转KML并保留标注
  • KTV 80.3.0| 电视K歌软件,完全免费,曲库丰富
  • 网站的设计思想网站开发iso9001
  • 湖北省工程建设协会网站镇江html5
  • 网上做兼职的网站有哪些平面设计网上怎么接单
  • HTML Help Workshop 中文 4.74版
  • 个人站长和企业网站新开的网站怎么做seo优化
  • Leetcode 3693. Climbing Stairs II
  • 【LaTeX】 9 LaTeX 表格制作
  • 深度学习在卫星遥感图像分类中的应用
  • QML学习笔记(二十五)QML的KeyNavigation和FocusScope
  • 女生做网站编辑好还是做效果图比较好的模型网站
  • HA 配置mqtt
  • 广州网站建设 致茂用游戏人物做网站属于侵权吗
  • Linux应用(7)——多线程服务器设计
  • 如何用PQC(后量子密码)实现HTTPS加密?——从算法选型到Nginx部署的完整实践指南
  • 【深度学习新浪潮】由Sora-2上线观察AI视频生成模型的研发进展(2025.10)
  • 网站在空间费用制作书签的意义
  • 云南省新农村建设网站山东网页制作网站
  • 网上摄影工作室|基于SpringBoot和Vue的网上摄影工作室(源码+数据库+文档)
  • 江苏省建设工程竣工备案网站学校门户网站的作用