百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程文章 > 正文

一文读懂什么是进程(进程是指什么意思)

qiyuwang 2025-03-20 18:33 11 浏览 0 评论

什么是程序?

程序:可执行文件或者包含一堆可运行CPU指令的和数据


什么是进程?

进程 = 程序 + 执行

进程是执行中的程序,除了可执行代码外还包含进程的活动信息和数据,比如用来存放函数变量、局部变量、返回值的用户栈,存放进程相关数据的数据段,内核中进程间切换的内核栈,动态分配的堆。

进程是系统分配资源的基本单位(内存、CPU时间片)

进程是用来实现多进程并发执行的一个实体,实现对CPU的虚拟化,让每个进程感觉都拥有一个CPU,核心技术就是上下文切换和进程调度。

早期操作系统程序都是单个运行的,CPU利用率低下,为了提高CPU的利用率,加载多个程序到内存并发运行,在单核CPU中这种属于伪并发。

其实在同一时间只运行一个程序


进程控制块描述符

进程控制块描述符task_struct主要包含:

进程状态(state):表示进程当前的状态,比如运行、睡眠、停止等。

/* Used in tsk->__state: */
#define TASK_RUNNING  0x00000000
#define TASK_INTERRUPTIBLE  0x00000001
#define TASK_UNINTERRUPTIBLE  0x00000002
#define __TASK_STOPPED  0x00000004
#define __TASK_TRACED   0x00000008
/* Used in tsk->exit_state: */
#define EXIT_DEAD   0x00000010
#define EXIT_ZOMBIE   0x00000020
#define EXIT_TRACE          (EXIT_ZOMBIE | EXIT_DEAD)


进程调度信息(sched_info):包括进程的调度策略、优先级等信息

  int   on_rq;
  int   prio;
  int   static_prio;
  int   normal_prio;
  unsigned int  rt_priority;
  struct sched_entity   se;
  struct sched_rt_entity  rt;
  struct sched_dl_entity  dl;
  struct sched_dl_entity  *dl_server;
  const struct sched_class  *sched_class;

on_rq:表示进程是否在就绪队列中,即是否正在等待被调度执行。
prio:表示进程的动态优先级。
static_prio:表示进程的静态优先级。
normal_prio:表示进程的普通优先级。
rt_priority:表示实时进程的优先级。
se:sched_entity 结构体,用于普通进程的调度实体。
rt:sched_rt_entity 结构体,用于实时进程的调度实体。
dl:sched_dl_entity 结构体,用于周期性实时进程的调度实体。
dl_server:指向调度该进程的周期性实时进程的指针。
sched_class:指向调度类(sched_class)的指针,用于确定进程的调度策略和行为。

进程标识符(pid):唯一标识一个进程的数字标识符,内核使用bitmap保证进程分配唯一的pid。

一个线程组中所有的线程使用和线程组组长相同的pid,它会被存进tgid中

使用getpid()系统调用获取pid

  pid_t   pid;
  pid_t   tgid;

进程堆栈(stack):用于保存进程的函数调用栈信息。

 void  *stack;

引用计数(usage):用于跟踪进程的引用计数,确保在不再需要时能够正确释放资源。

refcount_t  usage;

进程标志位(flags):存储进程的各种标志位信息,比如是否在运行、是否被挂起等。

unsigned int  flags;

多处理器支持字段(on_cpu、wake_entry):用于处理多处理器环境下的调度和唤醒操作。

#ifdef CONFIG_SMP
  int             on_cpu;
  struct __call_single_node   wake_entry;
  unsigned int            wakee_flips;
  unsigned long           wakee_flip_decay_ts;
  struct task_struct      *last_wakee;

    /*
     * recent_used_cpu is initially set as the last CPU used by a task
     * that wakes affine another task. Waker/wakee relationships can
     * push tasks around a CPU where each wakeup moves to the next one.
     * Tracking a recently used CPU allows a quick search for a recently
     * used CPU that may be idle.
     */
  int             recent_used_cpu;
  int             wake_cpu;
#endif

进程间家庭关系

    /*
     * Pointers to the (original) parent process, youngest child, younger sibling,
     * older sibling, respectively.  (p->father can be replaced with
     * p->real_parent->pid)
     */

    /* Real parent process: */
  struct task_struct __rcu  *real_parent;

    /* Recipient of SIGCHLD, wait4() reports: */
  struct task_struct __rcu  *parent;

    /*
     * Children/sibling form the list of natural children:
     */
  struct list_head  children;
  struct list_head  sibling;
  struct task_struct  *group_leader;


进程的5种状态

创建态(New):当进程刚被创建时,处于创建态。在这个阶段,操作系统正在为进程分配资源和初始化进程控制块等信息。
就绪态(Ready):进程已经准备好运行,但由于还未获得处理器资源,暂时无法执行。进程在就绪队列中等待被调度执行。
运行态(Running):进程正在执行指令,占用 CPU 资源。在任意时刻,每个 CPU 上只能有一个进程处于运行态。
阻塞态(Blocked):进程由于等待某种事件(如 I/O 操作完成、信号量变为非零等)而暂时无法继续执行,进入阻塞态。在等待期间,进程不占用 CPU 资源。
终止态(Terminated):进程执行完毕或被操作系统终止后,进入终止态。在终止态下,进程的资源被释放,但仍保留进程控制块等信息,直到被操作系统回收。



查看进程状态

static void show_task(struct task_struct *volatile tsk)
{
  unsigned int p_state = READ_ONCE(tsk->__state);
  char state;

    /*
     * Cloned from kdb_task_state_char(), which is not entirely
     * appropriate for calling from xmon. This could be moved
     * to a common, generic, routine used by both.
     */
  state = (p_state == TASK_RUNNING) ? 'R' :
        (p_state & TASK_UNINTERRUPTIBLE) ? 'D' :
        (p_state & TASK_STOPPED) ? 'T' :
        (p_state & TASK_TRACED) ? 'C' :
        (tsk->exit_state & EXIT_ZOMBIE) ? 'Z' :
        (tsk->exit_state & EXIT_DEAD) ? 'E' :
        (p_state & TASK_INTERRUPTIBLE) ? 'S' : '?';

  printf("%16px %16lx %16px %6d %6d %c %2d %s\n", tsk,
  tsk->thread.ksp, tsk->thread.regs,
  tsk->pid, rcu_dereference(tsk->parent)->pid,
  state, task_cpu(tsk),
  tsk->comm);
}


init_task

初始化了 init_task 结构体的各个成员,包括线程信息、栈信息、调度优先级、CPU 信息、内存管理、信号处理、文件系统等等。

这些成员变量记录了进程的状态、资源分配、调度信息等重要内容。

struct task_struct init_task __aligned(L1_CACHE_BYTES) = {
    .__state    = 0,
    .stack      = init_stack,
    .usage      = REFCOUNT_INIT(2),
    .flags      = PF_KTHREAD,
    .prio       = MAX_PRIO - 20,
    .static_prio    = MAX_PRIO - 20,
    .normal_prio    = MAX_PRIO - 20,
    .policy     = SCHED_NORMAL,
    .cpus_ptr   = &init_task.cpus_mask,
    .user_cpus_ptr  = NULL,
    .cpus_mask  = CPU_MASK_ALL,
    .nr_cpus_allowed= NR_CPUS,
    .mm     = NULL,
    .active_mm  = &init_mm,
    .faults_disabled_mapping = NULL,
    .restart_block  = {
        .fn = do_no_restart_syscall,
    },
    .se     = {
        .group_node     = LIST_HEAD_INIT(init_task.se.group_node),
    },
    .rt     = {
        .run_list   = LIST_HEAD_INIT(init_task.rt.run_list),
        .time_slice = RR_TIMESLICE,
    },
    .tasks      = LIST_HEAD_INIT(init_task.tasks),
    .ptraced    = LIST_HEAD_INIT(init_task.ptraced),
    .ptrace_entry   = LIST_HEAD_INIT(init_task.ptrace_entry),
    .real_parent    = &init_task,
    .parent     = &init_task,
    .children   = LIST_HEAD_INIT(init_task.children),
    .sibling    = LIST_HEAD_INIT(init_task.sibling),
    .group_leader   = &init_task,
  RCU_POINTER_INITIALIZER(real_cred, &init_cred),
  RCU_POINTER_INITIALIZER(cred, &init_cred),
    .comm       = INIT_TASK_COMM,
    .thread     = INIT_THREAD,
    .fs     = &init_fs,
    .files      = &init_files,
    .signal     = &init_signals,
    .sighand    = &init_sighand,
    .nsproxy    = &init_nsproxy,
    .pending    = {
        .list = LIST_HEAD_INIT(init_task.pending.list),
        .signal = {{0}}
    },
    .blocked    = {{0}},
    .alloc_lock = __SPIN_LOCK_UNLOCKED(init_task.alloc_lock),
    .journal_info   = NULL,
  INIT_CPU_TIMERS(init_task)
    .pi_lock    = __RAW_SPIN_LOCK_UNLOCKED(init_task.pi_lock),
    .timer_slack_ns = 50000, /* 50 usec default slack */
    .thread_pid = &init_struct_pid,
    .thread_node    = LIST_HEAD_INIT(init_signals.thread_head),


  INIT_PREV_CPUTIME(init_task)
};
EXPORT_SYMBOL(init_task);


进程创建函数



fork

fork函数用于创建一个新的进程,新进程是调用进程(父进程)的副本。

函数名称:fork
头文件:#include
返回类型:pid_t(进程ID类型)
参数:无参数
返回值:在父进程中,fork返回新创建子进程的进程ID(PID),在子进程中,fork返回0。如果出现错误,fork返回-1。

通过调用fork函数,父进程会创建一个子进程,子进程会继承父进程的数据、堆栈、文件描述符等信息,但是它们各自有独立的内存空间。


vfork

vfork函数用于创建一个新的进程,但与fork不同的是,vfork保证子进程先运行,调用do_fork时比fork多了两个标志位,
CLONE_VFORK和CLONE_VM,分别代表父进程被挂起,直到子进程释放资源和父子进程运行在相同的内存空间。

函数名称:vfork
头文件:#include
返回类型:pid_t(进程ID类型)
参数:无参数

返回值:在父进程中,vfork返回新创建子进程的进程ID(PID),在子进程中,vfork返回0。如果出现错误,vfork返回-1。


clone

clone函数用于创建一个新的线程或进程,可以指定不同的选项来控制创建的行为

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *ptid, unsigned long newtls, pid_t *ctid */ );
函数名称:clone
头文件:#include
返回类型:int
参数:
fn:指向新线程/进程入口点函数的指针
child_stack:子进程/线程的栈指针
flags:用于指定创建新线程/进程的选项
arg:传递给新线程/进程入口点函数的参数
...:可选参数,包括ptid、newtls和ctid
返回值:成功时返回新线程/进程的PID(对于线程,返回0表示成功),失败时返回-1。


kthread_create

kthread_create函数用于创建一个新的内核线程,该线程在内核空间中运行,可以执行内核级别的任务。

通过调用kthread_create函数,可以在Linux内核中创建一个新的内核线程,用于执行后台任务、定时任务等内核级别的工作。内核线程与用户空间的线程有所不同,它们在内核空间中运行,可以访问内核数据结构和执行特权操作。

struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...);
函数名称:kthread_create
返回类型:struct task_struct *(指向内核线程结构体的指针)
参数:threadfn:指向内核线程函数的指针,即内核线程的入口点函数
data:传递给内核线程函数的参数
namefmt:内核线程的名称格式字符串
...:可变参数,用于指定内核线程的调度优先级等其他选项
功能:
返回值:成功时返回指向新创建内核线程的task_struct结构体指针,失败时返回NULL。


do_fork


fork/vfork/clone/kthread_create底层都是通过调用do_fork创建进程

long do_fork(unsigned long clone_flags,unsigned long stack_start, unsigned long stack_size,int __user *parent_tidptr,int __user *child_tidptr)

{

return _do_fork(clone_flags, stack_start, stack_size,

parent_tidptr, child_tidptr, 0);

}

clone_flags:用于指定创建新进程/线程的选项,包括是否共享地址空间、文件描述符等。
stack_start:新进程/线程的栈起始地址。
stack_size:新进程/线程的栈大小。
parent_tidptr:指向父进程/线程的线程ID的指针。
child_tidptr:指向子进程/线程的线程ID的指针。

子进程不会继承的一些主要属性和资源:

进程ID(PID):子进程会有自己独立的进程ID,不会继承父进程的PID。
父进程ID(PPID):子进程的父进程ID会被设置为创建它的父进程的PID,而不是继承父进程的PPID。
信号处理器:子进程不会继承父进程设置的信号处理器,它们会有各自独立的信号处理器。
文件锁:子进程不会继承父进程设置的文件锁。
定时器:子进程不会继承父进程设置的定时器。
共享内存和信号量:子进程不会继承父进程的共享内存和信号量。
资源限制:子进程不会继承父进程设置的资源限制,如文件打开数限制等。

execve:
功能:execve系统调用用于加载并执行一个新的程序,替换当前进程的映像(代码和数据)为新程序的映像。
参数:execve接受三个参数,分别是要执行的程序路径、命令行参数数组和环境变量数组。
返回值:如果execve执行成功,则不会返回,因为当前进程的映像已被替换为新程序的映像;如果出现错误,则返回-1。
特点:execve会将当前进程的映像替换为新程序的映像,新程序开始执行时,会继承当前进程的PID等信息,但不会保留原有进程的任何状态。

写时复制技术

在传统的unix操作系统中,创建新进程时会复制父进程所拥有的资源,但是子进程不一定需要父进程的全部资源。

在现代的操作系统中采用了写时复制copy on write,COW技术,在创建子进程时只需要复制进程的地址空间页表,

只读共享进程地址空间,当父子进程其中一方需要修改页面数据时,触发缺页异常,此时才会从复制内容。


终止进程

1.程序主动主动调用exit退出

2.进程收到SIGKILL信号

kill -15 PID # 发送SIGTERM信号

kill -9 PID # 发送SIGKILL信号

3.触发内核异常

4.收到不能处理的信号


僵尸进程和孤儿进程

僵尸进程:
定义:当一个进程终止,但其父进程没有及时处理该进程的终止状态信息(称为SIGCHLD信号),导致该进程的进程描述符仍然存在,但进程已经终止,此时该进程就成为僵尸进程。
特点:僵尸进程不占用系统资源,但会占用进程表中的一个条目。
解决方法:父进程应该及时处理SIGCHLD信号,通过调用wait()或waitpid()等系统调用来回收子进程的资源,防止子进程变成僵尸进程。


孤儿进程:
定义:当一个进程的父进程提前终止,而该进程本身还在运行,此时该进程成为孤儿进程。
特点:孤儿进程会被init进程(PID为1)接管,init进程会成为孤儿进程的新父进程。
影响:孤儿进程的父进程终止后,孤儿进程会继续运行,直到自己终止或被init进程接管。



0号进程

**0、1号进程代码来源0.11版本内核**
0号进程通常指的是内核线程(kernel thread)或者是调度进程(scheduler process),其PID为0。这个进程在系统启动时就已经存在,并且在整个系统运行期间都存在。
0号进程通常被称为内核线程,因为它在内核空间运行,不属于用户空间的任何进程。它在系统中扮演着重要的角色,负责系统的调度、内存管理、I/O操作等核心功能。由于它是内核的一部分,因此没有对应的用户空间程序,也不会被用户直接创建或终止。
在Linux系统中,0号进程通常是系统中所有进程的祖先,即所有进程的父进程。当一个进程的父进程终止时,该进程会成为孤儿进程,并被0号进程(init进程,PID为1)接管。


sched_init();                           // 初始化0进程
void sched_init(void)
{
  int i;
  struct desc_struct * p;

  if (sizeof(struct sigaction) != 16)
  panic("Struct sigaction MUST be 16 bytes");
  set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss)); // 设置TSS
  set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt)); // 设置LDT
  p = gdt+2+FIRST_TSS_ENTRY;                               // 获取TSS      
  for(i=1;ia=p->b=0;
  p++;
  p->a=p->b=0;
  p++;
    }
/* Clear NT, so that we won't have troubles with that later on */
  __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); // 清空NT
  ltr(0);     // 挂载TSS到TR寄存器
  lldt(0);    // 挂载LDTR寄存器
  // 设置定时器模式、以及设置高低位组成一个周期
  outb_p(0x36,0x43);      /* binary, mode 3, LSB/MSB, ch 0 */
  outb_p(LATCH & 0xff , 0x40);    /* LSB */
  outb(LATCH >> 8 , 0x40);    /* MSB */  
  set_intr_gate(0x20,&timer_interrupt);   // 开启定时器中断
  outb(inb_p(0x21)&~0x01,0x21);           // 允许时钟中断
  set_system_gate(0x80,&system_call);     // 设置系统调用的入口
}


1号进程

在Linux系统中,1号进程通常指的是init进程,其PID为1。init进程是系统中所有进程的祖先进程,是系统启动时由内核创建的第一个用户级进程。init进程负责系统的初始化、进程的管理和系统的关机等任务。

void main(void)     /* This really IS void, no error here. */
{           /* The startup routine assumes (well, ...) this */
  mem_init(main_memory_start,memory_end); // 初始化内存映射
  trap_init();                            // 初始化中断捕获
  blk_dev_init();                         // 块设备初始化
  chr_dev_init();                         // 字符设备初始化
  tty_init();                             // 终端初始化
  time_init();                            // 时间初始化
  sched_init();                           // 初始化0进程
  buffer_init(buffer_memory_end);         // 缓冲区初始化
  hd_init();                              // 初始化硬盘
  floppy_init();                          // 初始化软盘
  sti();                                  // 开启全局中断
  move_to_user_mode();                    // 将进程0特权调到3级
  if (!fork()) {      /* we count on this going ok */
  init();                             // 子进程进行初始化
    }


void init(void)
{
  int pid,i;                              // pid用于fork

  setup((void *) &drive_info);            // 配置系统,包括磁盘、文件系统
    (void) open("/dev/tty0",O_RDWR,0);      // 打开tty文件,此时是标准输入设备文件
    (void) dup(0);                          // 从tty复制句柄,打开标准输出设备文件
    (void) dup(0);                          // 继续复制句柄,打开标准错误输出设备
  printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
  NR_BUFFERS*BLOCK_SIZE);
  printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
  if (!(pid=fork())) {                    // 到这里就要启动进程2了
  // fs/open.c
  close(0);                           // 进程2关闭输入
  if (open("/etc/rc",O_RDONLY,0))     // 使用/etc/rc替换输入设备,加载一些开机需要执行的东西
  _exit(1);                       // 替换失败就寄了
  // do_execve(fs/exec.c)
  execve("/bin/sh",argv_rc,envp_rc);  // 执行shell,参数分别是shell执行参数(="NULL")其环境变量(="/")
  // 由于输入已经改成了/etc/rc文件了,所以这里在运行/etc/rc的内容
  _exit(2);
    }
  if (pid>0)                              // 进程1暂停工作,等待子进程工作完成(子进程只有进程2)
  while (pid != wait(&i))             // 暂不深入wait -> 补充: 进程2退出了,回到了这里,pid = 2
            /* nothing */;
  while (1) {
  if ((pid=fork())<0) {               // 继续fork
  printf("Fork failed in init\r\n");
  continue;
        }
  if (!pid) {                         // 新的子进程
  close(0);close(1);close(2);     // 关闭了标准输入、标准输出、异常输出
  setsid();                      
            (void) open("/dev/tty0",O_RDWR,0);  // 打开了tty0
            (void) dup(0);                      // 标准输入
            (void) dup(0);                      // 标准输出
  _exit(execve("/bin/sh",argv,envp)); // 新进程打开sh,这里已经是用户可以控制的时候了
        }
  while (1)
  if (pid == wait(&i))                // 进程1重新进入等待
  break;
  printf("\n\rchild %d died with code %04x\n\r",pid,i);
  sync();
    }
  _exit(0);   /* NOTE! _exit, not exit() */
}
 

相关推荐

# 安装打开 ubuntu-22.04.3-LTS 报错 解决方案

#安装打开ubuntu-22.04.3-LTS报错解决方案WslRegisterDistributionfailedwitherror:0x800701bcError:0x80070...

利用阿里云镜像在ubuntu上安装Docker

简介:...

如何将Ubuntu Kylin(优麒麟)19.10系统升级到20.04版本

UbuntuKylin系统使用一段时间后,有新的版本发布,如何将现有的UbuntuKylin系统升级到最新版本?可以通过下面的方法进行升级。1.先查看相关的UbuntuKylin系统版本情况。使...

Ubuntu 16.10内部代号确认为Yakkety Yak

在正式宣布Ubuntu16.04LTS(XenialXerus)的当天,Canonical创始人MarkShuttleworth还非常开心的在个人微博上宣布Ubuntu下个版本16.10的内...

如何在win11的wsl上装ubuntu(怎么在windows上安装ubuntu)

在Windows11的WSL(WindowsSubsystemforLinux)上安装Ubuntu非常简单。以下是详细的步骤:---...

Win11学院:如何在Windows 11上使用WSL安装Ubuntu

IT之家2月18日消息,科技媒体pureinfotech昨日(2月17日)发布博文,介绍了3中简便的方法,让你轻松在Windows11系统中,使用WindowsSubs...

如何查看Linux的IP地址(如何查看Linux的ip地址)

本头条号每天坚持更新原创干货技术文章,欢迎关注本头条号"Linux学习教程",公众号名称“Linux入门学习教程"。...

怎么看电脑系统?(怎么看电脑系统配置)

要查看电脑的操作系统信息,可以按照以下步骤操作,根据不同的操作系统选择对应的方法:一、Windows系统通过系统属性查看右键点击桌面上的“此电脑”(或“我的电脑”)图标,选择“属性”。在打开的...

如何查询 Linux 内核版本?这些命令一定要会!

Linux内核是操作系统的核心,负责管理硬件资源、调度进程、处理系统调用等关键任务。不同的内核版本可能支持不同的硬件特性、提供新的功能,或者修复了已知的安全漏洞。以下是查询内核版本的几个常见场景:...

深度剖析:Linux下查看系统版本与CPU架构

在Linux系统管理、维护以及软件部署的过程中,精准掌握系统版本和CPU架构是极为关键的基础操作。这些信息不仅有助于我们深入了解系统特性、判断软件兼容性,还能为后续的软件安装、性能优化提供重要依据。接...

504 错误代码解析与应对策略(504错误咋解决)

在互联网的使用过程中,用户偶尔会遭遇各种错误提示,其中504错误代码是较为常见的一种。504错误并非意味着网站被屏蔽,它实际上是指服务器在规定时间内未能从上游服务器获取响应,专业术语称为“Ga...

猎聘APP和官网崩了?回应:正对部分职位整改,临时域名可登录

10月12日,有网友反映猎聘网无法打开,猎聘APP无法登录。截至10月14日,仍有网友不断向猎聘官方微博下反映该情况,而猎聘官方微博未发布相关情况说明,只是在微博内对反映该情况的用户进行回复,“抱歉,...

域名解析的原理是什么?域名解析的流程是怎样的?

域名解析是网站正常运行的关键因素,因此网站管理者了解域名解析的原理和流程对于做好域名管理、解决常见解析问题,保障网站的正常运转十分必要。那么域名解析的原理是什么?域名解析的流程是怎样的?接下来,中科三...

Linux无法解析域名的解决办法(linux 不能解析域名)

如果由于误操作,删除了系统原有的dhcp相关设置就无法正常解析域名。  此时,需要手动修改配置文件:  /etc/resolv.conf  将域名解析服务器手动添加到配置文件中  该文件是DNS域名解...

域名劫持是什么?(域名劫持是什么)

域名劫持是互联网攻击的一种方式,通过攻击域名解析服务器(DNS),或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的地址从而实现用户无法访问目标网站的目的。说的直白些,域名劫持,就是把互...

取消回复欢迎 发表评论: