电子产业一站式赋能平台

PCB联盟网

搜索
查看: 92|回复: 0
收起左侧

一文读懂什么是进程

[复制链接]

317

主题

317

帖子

3149

积分

四级会员

Rank: 4

积分
3149
发表于 2024-11-12 11:52:00 | 显示全部楼层 |阅读模式
什么是程序?程序:可执行文件或者包含一堆可运行CPU指令的和数据

什么是进程?进程 = 程序 + 执行
进程是执行中的程序,除了可执行代码外还包含进程的活动信息和数据,比如用来存放函数变量、局部变量、返回值的用户栈,存放进程相关数据的数据段,内核中进程间切换的内核栈,动态分配的堆。
进程是系统分配资源的基本单位(内存、CPU时间片)
进程是用来实现多进程并发执行的一个实体,实现对CPU的虚拟化,让每个进程感觉都拥有一个CPU,核心技术就是上下文切换和进程调度。
早期操作系统程序都是单个运行的,CPU利用率低下,为了提高CPU的利用率,加载多个程序到内存并发运行,在单核CPU中这种属于伪并发。
其实在同一时间只运行一个程序

0q2ntwqaodt640133689456.png

0q2ntwqaodt640133689456.png

进程控制块描述符进程控制块描述符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):进程执行完毕或被操作系统终止后,进入终止态。在终止态下,进程的资源被释放,但仍保留进程控制块等信息,直到被操作系统回收。

d0umtjrlsti640133689556.png

d0umtjrlsti640133689556.png


查看进程状态
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
", 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);
进程创建函数

vp4u0flqegz640133689656.png

vp4u0flqegz640133689656.png

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
\r",NR_BUFFERS,
  NR_BUFFERS*BLOCK_SIZE);
  printf("Free mem: %d bytes
\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())
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


联系客服 关注微信 下载APP 返回顶部 返回列表