⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 进程.txt

📁 讲解linux内核 进程调度 部分经典讲义
💻 TXT
📖 第 1 页 / 共 5 页
字号:
进程  
      进程

目 录
  进程 
    信号 
    sched.c 
    进程信号队列 
    SMP 
    内核线程页目录的借用 
    代码分析 
    线程 
    进程描述符 
    init进程从内核态切换到用户态 
    SET_LINKS 
    REMOVE_LINKS 
    get_wchan() 
    sigframe的结构 
    rt_sigframe结构 
    信号队列的结构 
    内核线程简介 
    进程切换简介 
    同步机制




进程

一  进程调度
    进程的状态([include/linux.h]):
TASK_RUNNING, it means that it is in the "Ready List"
TASK_INTERRUPTIBLE, task waiting for a signal or a resource (sleeping)
TASK_UNINTERRUPTIBLE, task waiting for a resource (sleeping), it is in same 
"Wait Queue"
TASK_ZOMBIE, task child without father
TASK_STOPPED, task being debugged
       ______________     CPU Available     ______________
      |              |  ---------------->  |              |
      | TASK_RUNNING |                     | Real Running |
      |______________|  <----------------  |______________|
                           CPU Busy
            |   /|\
Waiting for |    | Resource
Resource   |    | Available
           \|/   |
    ______________________
   |                      |
   | TASK_INTERRUPTIBLE / |
   | TASK-UNINTERRUPTIBLE |
   |______________________|
                     Main Multitasking Flow
    从系统内核的角度看来,一个进程仅仅是进程控制表(process table)中的一项。进程控制表中的每一项都是一个task_struct 
结构,而task_struct 
结构本身是在include/linux/sched.h中定义的。在task_struct结构中存储各种低级和高级的信息,包括从一些硬件设备的寄存器拷贝到进程的工作目录的链接点。
    
进程控制表既是一个数组,又是一个双向链表,同时又是一个树。其物理实现是一个包括多个指针的静态数组。此数组的长度保存在include/linux/tasks.h 
定义的常量NR_TASKS中,其缺省值为128,数组中的结构则保存在系统预留的内存页中。链表是由next_task 
和prev_task两个指针实现的,而树的实现则比较复杂。
    
系统启动后,内核通常作为某一个进程的代表。一个指向task_struct的全局指针变量current用来记录正在运行的进程。变量current只能由kernel/sched.c中的进程调度改变。当系统需要查看所有的进程时,则调用for_each_task,这将比系统搜索数组的速度要快得多。
二、用户进程和内核线程
    某一个进程只能运行在用户方式(user mode)或内核方式(kernel 
mode)下。用户程序运行在用户方式下,而系统调用运行在内核方式下。在这两种方式下所用的堆栈不一样:用户方式下用的是一般的堆栈,而内核方式下用的是固定大小的堆栈(一般为一个内存页的大小)
    尽管linux是一个宏内核系统,内核线程依然存在,以便并行地处理一些内核的“家务室”。这些任务不占用USER 
memory(用户空间),而仅仅使用KERNEL memory。和其他内核模块一样,它们也在高级权限(i386系统中的RING 
0)下工作作。内核线程是被kernel_thread 
[arch/i386/kernel/process]创建的,它又通过调用著名的clone系统调用[arch/i386/kernel/process.c] 
(类似fork系统调用的所有功能都是由它最终实现):
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
        long retval, d0;
        __asm__ __volatile__(
                "movl %%esp,%%esi\n\t"
                "int $0x80\n\t"         /* Linux/i386 system call */
                "cmpl %%esp,%%esi\n\t"  /* child or parent? */
                "je 1f\n\t"             /* parent - jump */
                /* Load the argument into eax, and push it.  That way, it does
                 * not matter whether the called function is compiled with
                 * -mregparm or not.  */
                "movl %4,%%eax\n\t"
                "pushl %%eax\n\t"
                "call *%5\n\t"          /* call fn */
                "movl %3,%0\n\t"        /* exit */
                "int $0x80\n"
                "1:\t"
                :"=&a" (retval), "=&S" (d0)
                :"0" (__NR_clone), "i" (__NR_exit),
                 "r" (arg), "r" (fn),
                 "b" (flags | CLONE_VM)
                : "memory");
        return retval;
}
    一旦调用,我们就有了一个新的任务(Task) (一般PID都很小, 例如2,3,等) 
等待一个响应很慢的资源,例如swap或者usb事件,以便同步。下面是一些最常用的内核线程(你可以用ps x命令):
PID      COMMAND
1        init
2        keventd
3        kswapd
4        kreclaimd
5        bdflush
6        kupdated
7        kacpid
67        khubd
     init内核线程也是启动以后最初的进程。 它会调用其它用户模式的任务,(/etc/inittab)例如控制台守护进程(daemons), 
tty守护进程以及网络守护进程(rc脚本)。
下面是一个典型的内核线程kswapd [mm/vmscan.c].
kswapd是被clone()建立的 [arch/i386/kernel/process.c]''
|do_initcalls
   |kswapd_init
      |kernel_thread
         |syscall fork (in assembler)
·do_initcalls [init/main.c]
·kswapd_init [mm/vmscan.c]
·kernel_thread [arch/i386/kernel/process.c]
三 进程创建,运行和消失
    Linux系统使用系统调用fork( )来创建一个进程,使用exit( )来结束进程。fork( )和exit( 
)的源程序保存在kernel/fork.c and kernel/exit.c中。fork( )的主要任务是初始化要创建进程的数据结构,其主要的步骤有:
1)申请一个空闲的页面来保存task_struct。
2)查找一个空的进程槽(find_empty_process( ))。
3)为kernel_stack_page申请另一个空闲的内存页作为堆栈。
4)将父进程的LDT表拷贝给子进程。
5)复制父进程的内存映射信息。
6)管理文件描述符和链接点。
|sys_fork
   |do_fork
      |alloc_task_struct
         |__get_free_pages
       |p->state = TASK_UNINTERRUPTIBLE
       |copy_flags
       |p->pid = get_pid
       |copy_files
       |copy_fs
       |copy_sighand
       |copy_mm // should manage CopyOnWrite (I part)
          |allocate_mm
          |mm_init
             |pgd_alloc -> get_pgd_fast
                |get_pgd_slow
          |dup_mmap
             |copy_page_range
                |ptep_set_wrprotect
                   |clear_bit // set page to read-only
          |copy_segments // For LDT
       |copy_thread
          |childregs->eax = 0
          |p->thread.esp = childregs // child fork returns 0
          |p->thread.eip = ret_from_fork // child starts from fork exit
       |retval = p->pid // parent fork returns child pid
       |SET_LINKS // insertion of task into the list pointers
       |nr_threads++ // Global variable
       |wake_up_process(p) // Now we can wake up just created child
       |return retval
·sys_fork [arch/i386/kernel/process.c]
·do_fork [kernel/fork.c]
·alloc_task_struct [include/asm/processor.c]
·__get_free_pages [mm/page_alloc.c]
·get_pid [kernel/fork.c]
·copy_files
·copy_fs
·copy_sighand
·copy_mm
·allocate_mm
·mm_init
·pgd_alloc -> get_pgd_fast [include/asm/pgalloc.h]
·get_pgd_slow
·dup_mmap [kernel/fork.c]
·copy_page_range [mm/memory.c]
·ptep_set_wrprotect [include/asm/pgtable.h]
·clear_bit [include/asm/bitops.h]
·copy_segments [arch/i386/kernel/process.c]
·copy_thread
·SET_LINKS [include/linux/sched.h]
·wake_up_process [kernel/sched.c]
    撤消一个进程可能稍微复杂些,因为撤消子进程必须通知父进程。另外,使用kill( )也可以结束一个进程。sys_kill( )、sys_wait( 
)和sys_exit( )都保存在文件exit.c中。
    使用fork ( )创建一个进程后,程序的两个拷贝都在运行。通常一个拷贝使用exec ( )调用另一个拷贝。系统调用exec ( 
)负责定位可执行文件的二进制代码,并负责装入和运行。Linux系统中的exec ( 
)通过使用linux_binfmt结构支持多种二进制格式。每种二进制格式都代表可执行代码和链接库。linux 
_binfmt结构种包含两个指针,一个指向装入可执行代码的函数,另一个指向装入链接库的函数。
    Unix系统提供给程序员6种调用exec( ) 的方法。其中的5种是作为库函数实现,而sys_execve( 
)是由系统内核实现的。它执行一个十分简单的任务:装入可执行文件的文件头,并试图执行它。如果文件的头两个字节是#! 
,那么它就调用在文件第一行中所指定的解释器,否则,它将逐个尝试注册的二进制格式。 
[目录]




信号

struct semaphore {
        atomic_t count; 进程抓取semaphore时减1
        int sleepers; 抓取semaphore失败时增1
        wait_queue_head_t wait; semaphore的等待队列
};
        down(&sem) 编绎成:
        movl $sem,% ecx        通过寄存器ecx向__down函数传递sem指针
        decl sem
        js 2f 如果为负值,表示semaphore已被占用,执行__down_failed过程
1:
由于出现semaphore竞争的可能性比较小,将分支代码转移到.text.lock段,以缩短正常的指令路径.
.section .text.lock,"ax"
2:        call __down_failed
        jmp 1b
.previous
        ...
        up(&sem) 编绎成:
        movl $sem,% ecx
        incl sem
        jle 2f 如果小于或等于0,表示该semaphore有进程在等待,就去调用__up_wakeup
1:
.section .text.lock,"ax"
2:        call __up_wakeup
        jmp 1b
.previous
        ...
__down_failed:
        pushl % eax
        pushl % edx
        pushl % ecx ; eax,edx,ecx是3个可用于函数参数的寄存器
        call __down
        popl % ecx
        popl % edx
        popl % eax
        ret
__up_wakeup:
        pushl % eax
        pushl % edx
        pushl % ecx
        call __up
        popl % ecx
        popl % edx
        popl % eax
        ret
; semaphore.c
void __down(struct semaphore * sem)
{
        struct task_struct *tsk = current;
        DECLARE_WAITQUEUE(wait, tsk);
        tsk->state = TASK_UNINTERRUPTIBLE;
        add_wait_queue_exclusive(&sem->wait, &wait);
        // 将当前进程加入到该semaphore的等待队列中
        spin_lock_irq(&semaphore_lock);
        sem->sleepers++;
        for (;;) {
                int sleepers = sem->sleepers;
                /*
                * Add "everybody else" into it. They aren't
                * playing, because we own the spinlock.
                */
                // atomic_add_negative(int i,atomic_t *v)将i + v->counter相加,
                // 结果为负返回1,否则返回0
                if (!atomic_add_negative(sleepers - 1, &sem->count)) {
                // 如果(sleepers - 1 + sem->count.counter)非负,则说明
                // semaphore已经被释放,可以返回
                        sem->sleepers = 0;
                        break;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -