Linux 进程概述

浅谈Linux进程
作者:嵌入式学院武汉华嵌中心技术支持涂佩君


内容清单:

1.    Linux 内核内进程表示

2.    Linux 内进程管理

3.    Linux 内核创建一个进程

引言

 

     Linux的用户空间进程的创建和管理所涉及的原理与 UNIX有很多共同点,但也有一些特定于 Linux 的独特之处。在本文中,了解 Linux 进程的生命周期,探索用户进程创建、内存管理内幕。

Linux 是一种动态系统,能够适应不断变化的计算需求。Linux 计算需求的表现是以进程 的通用抽象为中心的。进程可以是短期的(从命令行执行的一个命令),也可以是长期的(一种网络服务)。因此,对进程及其调度进行一般管理就显得极为重要。

在用户空间,进程是由进程标识符(PID)表示的。从用户的角度来看,一个 PID 是一个数字值,可惟一标识一个进程。一个 PID 在进程的整个生命期间不会更改,但 PID 可以在进程销毁后被重新使用,所以对它们进行缓存并不见得总是理想的。

在用户空间,创建进程可以采用几种方式。可以执行一个程序(这会导致新进程的创建),也可以在程序内,调用一个 fork 或 exec 系统调用。fork 调用会导致创建一个子进程,而 exec 调用则会用新程序代替当前进程上下文。接下来,我将对这几种方法进行讨论以便您能很好地理解它们的工作原理。

具体介绍:

 

进程表示

    在 Linux 内核内,进程是由相当大的一个称为 task_struct 的结构表示的。此结构包含所有表示此进程所必需的数据,此外,还包含了大量的其他数据用来统计(accounting)和维护与其他进程的关系(父和子)。清单 1给出了 task_struct 的一小部分。task_struct ./linux/include/linux/sched.h

清单 1. task_struct 的一小部分

struct task_struct {

        volatile long state;// state 变量是一些表明任务状态的比特位

 

        void *stack;

        unsigned int flags;// flags 定义了很多指示符,表明进程是否正在被(PF_STARTING)//或退出(PF_EXITING),或是进程当前是否在分配内存(PF_MEMALLOC)

        int prio, static_prio;// static_prio 优先级

 

        struct list_head tasks;

 

        struct mm_struct *mm, *active_mm;// mm 代表的是进程的内存描述符, //active_mm 则是前一个进程的内存描述符(为改进上下文切换时间的一种优化)。

 

        pid_t pid;

        pid_t tgid;

 

        struct task_struct *real_parent;

 

char comm[TASK_COMM_LEN];// 可执行程序的名称

 

        struct thread_struct thread;// 标识进程的存储状态

 

        struct files_struct *files;

 

        ...

};

 

进程管理

    现在,让我们来看看如何在 Linux 内管理进程。在很多情况下,进程都是动态创建并由一个动态分配的 task_struct 表示。一个例外是 init进程本身,它总是存在并由一个静态分配的 task_struct 表示。在 ./linux/arch/i386/kernel/init_task.c 内可以找到这样的一个例子。Linux内所有进程的分配有两种方式。第一种方式是通过一个哈希表,由 PID 值进行哈希计算得到;第二种方式是通过双链循环表。循环表非常适合于对任务列表进行迭代。由于列表是循环的,没有头或尾;但是由于 init_task 总是存在,所以可以将其用作继续向前迭代的一个锚点。让我们来看一个遍历当前任务集的例子。

清单 2.发出任务信息的简单内核模块(procsview.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
 
static int __init init_module( void )
{
  struct task_struct *task = &init_task;
 
  do {
  printk( KERN_INFO "*** %s [%d] parent %s\n",
               task->comm, task->pid, task->parent->comm );
     } while ( (task = next_task(task)) != &init_task );
 
  return 0;
 
}
 
Static void __exit cleanup_module( void )
{
  return;
}
 
module_init(init_module);
module_exit(cleanup_module);
MODULE_LICENSE("GPL");
 
注意,还可以标识当前正在运行的任务。Linux 维护一个称为 current 的符号,代表的是当前运行的进程(类型是 task_struct)。

static inline struct task_struct * get_current(void)

{

       struct task_struct *current;

       __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));

       return current;

 }

#define current get_current()

附:最大进程数

在 Linux 内虽然进程都是动态分配的,但还是需要考虑最大进程数。在内核内最大进程数是由一个称为 max_threads 的符号表示的,它可以在 ./linux/kernel/fork.c 内找到。可以通过 /proc/sys/kernel/threads-max 的 proc 文件系统从用户空间更改此值。

进程创建

    如何从用户空间创建一个进程。用户空间任务和内核任务的底层机制是一致的,因为二者最终都会依赖于一个名为 do_fork的函数来创建新进程。在创建内核线程时,内核会调用一个名为 kernel_thread 的函数(参见 ./linux/arch/i386/kernel/process.c),此函数执行某些初始化后会调用 do_fork

创建用户空间进程的情况与此类似。在用户空间,一个程序会调用 fork,这会导致对名为 sys_fork的内核函数的系统调用(参见 ./linux/arch/i386/kernel/process.c)。

您可能已经看到过系统调用的模式了。在很多情况下,系统调用都被命名为 sys_*并提供某些初始功能以实现调用(例如错误检查或用户空间的行为)。实际的工作常常会委派给另外一个名为 do_*的函数。

kernel_thread函数的作用是产生一个新的线程,内核线程实际上就是一个共享父进程地址空间的进程,它有自己的系统堆栈.

假设我们在struct dev中定义了   struct task_struct *thread;

在初始化函数创建并启动该线程;

thread=kthread_run(kthread_fun,dev,"%s",dev ->name);

在注销函数中:

kthread_stop(dev->thread);//停止内核线程

附:

创建并启动线程的函数:
struct task_struct *kthread_run(int (*threadfn)(void *data),  void *data,
                                   const char *namefmt, ...);
 
线程一旦启动起来后,会一直运行,除非该线程主动调用do_exit函数,或者其他的进程调用kthread_stop函数,结束线程的运行。
 int kthread_stop(struct task_struct *thread);//kthread_stop() 通过发送信号给线程。
当然还可以使用kthread_create创建线程,但线程创建后,不会马上运行,而是需要将kthread_create() 返回的task_struct指针传给wake_up_process(),然后通过此函数运行线程。
struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char *namefmt, ...);
 

#define kthread_run(threadfn, data, namefmt, ...)      \
({            \
struct task_struct *__k         \
   = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__k))         \
   wake_up_process(__k);        \
__k;           \
})

可见这里的函数kthread_create()只是创建了内核线程,而最后启动是怎么启动的呢,我们看到了后面的wake_up_process()函数,没错就是这个函数启动了这个线程,让它在一开始就一直运行下去。知道遇见kthread_should_stop函数或者kthread_stop()函数。
原文地址:https://www.cnblogs.com/jeakon/p/2816818.html