内核代码阅读(20) - 进程

简介: 进程

进程的数据结构

TSS段

虽然Linux跳过了TSS,还是有必要了解这个硬件的。

Intel在32位上考虑了进程的管理和调度,添加了TSS段,记录了进程相关的关键性的信息。

同样的,TSS段也要有段描述表项,但是只能在GDT中。如果在LDT中则会产生一次GP。

TSS段所对应的寄存器是TR,和CS,DS一样,TR也有一个不可见的影子,每当一个段选择码加载到TR中,CPU会找到TR所选中的TSS段,并装入影子里。

Linux对TSS的使用

TSS的问题是每次切换进程都要重新装载TR和更新TSS段的影子,虽然代码中一条指令就完成了进程的切换,但是性能和灵活性不高。
Linux只在系统初始化一次TSS,进程切换并不切换TR。所以TSS是全局的资源,在SMP中每个CPU有一个TR值,一经初始化就不改变TR。
当系统进入内核态时,需要栈切换,内核的栈SS和ESP正好存储在当前进程的TSS中。由于内核运行在0级,所以对应TSS中的SS0和ESP0.

TSS初始化

#define INIT_TSS  {                                                \
        0,0, /* back_link, __blh */                                \
        sizeof(init_stack) + (long) &init_stack, /* esp0 */        \
        __KERNEL_DS, 0, /* ss0 */                                \
        0,0,0,0,0,0, /* stack1, stack2 */                        \
        0, /* cr3 */                                                \
        0,0, /* eip,eflags */                                        \
        0,0,0,0, /* eax,ecx,edx,ebx */                                \
        0,0,0,0, /* esp,ebp,esi,edi */                                \
        0,0,0,0,0,0, /* es,cs,ss */                                \
        0,0,0,0,0,0, /* ds,fs,gs */                                \
        __LDT(0),0, /* ldt */                                        \
        0, INVALID_IO_BITMAP_OFFSET, /* tace, bitmap */                \
        {~0, } /* ioperm */                                        \
    }
1) sizeof(init_stack) + (long) &init_stack
   esp0 指向init_statck的顶端。
2) __KERNEL_DS, 0,
   ss0 指向__KERNEL_DS
init_statck的定义:
#define init_stack        (init_task_union.stack)
union task_union init_task_union 
        __attribute__((__section__(".data.init_task"))) =
                { INIT_TASK(init_task_union.task) };
struct tss_struct init_tss[NR_CPUS] __cacheline_aligned = { [0 ... NR_CPUS-1] = INIT_TSS };
1) init_tss 数组大小是CPU的个数,其中每个tss_struct的内容都是一样的。

task_union和内核空间的栈

union task_union {
        struct task_struct task;
        unsigned long stack[INIT_TASK_SIZE/sizeof(long)];
    };
1) task_union
   这个union很关键,内核在分配一个task_struct的时候,实际上分配2个页面。
   页面的开始部分是task_struct结构体,剩下的部分是内核栈。
2) task_struct大小约1k,内核栈大小约7k。内核栈的大小是固定的。

和task_struct,内核栈相关的操作

task_struct的分配

#define alloc_task_struct() ((struct task_struct *) __get_free_pages(GFP_KERNEL,1))
    #define free_task_struct(p) free_pages((unsigned long) (p), 1)
1) alloc_task_struct
   实际上分配了2两个页面。
2) free_task_struct
   实际上调用了free_pages

current宏

static inline struct task_struct * get_current(void)
    {
        struct task_struct *current;
        __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
        return current;
    }
__asm__("andl %%esp,%0; " : "=r"(current) : "0"(~8191UL));
内核需要获取当前进程的结构体的指针时,把当前的esp和8k对齐就得到了8k的起始地址,也就是task_struct的地址。
current宏只使用了一条andl就得到了当前进程的结构体指针,而不是把task_struct指针存放在全局变量中,然后从内存取读。妙!!!

task_struct

struct task_struct {
        volatile long state;
        unsigned long flags;
        int sigpending;
        mm_segment_t addr_limit;        /* thread address space:
                                                 0-0xBFFFFFFF for user-thead
                                                0-0xFFFFFFFF for kernel-thread
                                        */
        struct exec_domain *exec_domain;
        volatile long need_resched;
        unsigned long ptrace;
        int lock_depth;
        long counter;
        long nice;
        unsigned long policy;
        struct mm_struct *mm;
        int has_cpu, processor;
        unsigned long cpus_allowed;
        struct list_head run_list;
        unsigned long sleep_time;
        struct task_struct *next_task, *prev_task;
        struct mm_struct *active_mm;
        struct linux_binfmt *binfmt;
        int exit_code, exit_signal;
        struct rlimit rlim[RLIM_NLIMITS];
        ...
    }
1) state
   进程当前的状态
   #define TASK_RUNNING                0
       这个进程可以被调度执行,并不是说这个进程正在执行
   #define TASK_INTERRUPTIBLE        1
       浅度睡眠 interruptible_sleep_on/wake_up_interruptible
   #define TASK_UNINTERRUPTIBLE        2
       深度睡眠 sleep_on/wake_up
   #define TASK_ZOMBIE                4
   #define TASK_STOPPED                8
      用于调试 SIGSTOP SIGCONT
2) flags
   用户进程管理的一些状态
3) sigpending
   表示进程收到了信号,但尚未处理。
4) couter
   用于进程调度
5) need_resched
   CPU从系统空间返回用户空间前夕需要进行一次调度。
6) addr_limit
   虚拟地址的上限。
7) struct rlimit rlim[RLIM_NLIMITS];
   进程的资源限制。
   #define RLIMIT_CPU        0                /* CPU time in ms */
   #define RLIMIT_FSIZE        1                /* Maximum filesize */
   #define RLIMIT_DATA        2                /* max data size */
   #define RLIMIT_STACK        3                /* max stack size */
   #define RLIMIT_CORE        4                /* max core file size */
   #define RLIMIT_RSS        5                /* max resident set size */
   #define RLIMIT_NPROC        6                /* max number of processes */
   #define RLIMIT_NOFILE        7                /* max number of open files */
   #define RLIMIT_MEMLOCK        8                /* max locked-in-memory address space */
   #define RLIMIT_AS        9                /* address space limit */
   #define RLIMIT_LOCKS        10                /* maximum file locks held */
   #define RLIM_NLIMITS        11

进程的创建和退出

Linux进程创建必须从一个已有的进程分裂出来。

fork + execve

fork和clone

fork原型
pid_t fork(void);
clone原型
in clone(int(*fn)(void *arg), void *child_stack, int flags, void *arg)
asmlinkage int sys_fork(struct pt_regs regs)
    {
        return do_fork(SIGCHLD, regs.esp, &regs, 0);
    }
asmlinkage int sys_clone(struct pt_regs regs)
    {
        unsigned long clone_flags;
        unsigned long newsp;
        clone_flags = regs.ebx;
        newsp = regs.ecx;
        if (!newsp)
        newsp = regs.esp;
        return do_fork(clone_flags, newsp, &regs, 0);
    }
asmlinkage int sys_vfork(struct pt_regs regs)
    {
        return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
    }
fork和clone最终调用到了do_fork。
do_fork是一个复杂的函数,留做下一篇慢慢分析。
突然发现这样一节一节的代码学习挺无趣的,到change的时候了。
相关文章
|
8月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
285 67
|
6月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
187 0
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
233 4
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
算法 调度
探索操作系统的心脏:内核与进程管理
【10月更文挑战第25天】在数字世界的复杂迷宫中,操作系统扮演着关键角色,如同人体中的心脏,维持着整个系统的生命力。本文将深入浅出地剖析操作系统的核心组件——内核,以及它如何通过进程管理来协调资源的分配和使用。我们将从内核的概念出发,探讨它在操作系统中的地位和作用,进而深入了解进程管理的机制,包括进程调度、状态转换和同步。此外,文章还将展示一些简单的代码示例,帮助读者更好地理解这些抽象概念。让我们一起跟随这篇文章,揭开操作系统神秘的面纱,理解它如何支撑起我们日常的数字生活。
|
算法 调度 Python
探索操作系统的内核——一个简单的进程调度示例
【9月更文挑战第17天】在这篇文章中,我们将深入探讨操作系统的核心组件之一——进程调度。通过一个简化版的代码示例,我们将了解进程调度的基本概念、目的和实现方式。无论你是初学者还是有一定基础的学习者,这篇文章都将帮助你更好地理解操作系统中进程调度的原理和实践。
|
9月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
9月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
654 5
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能