【Linux内核设计与实现】第三章——进程管理01
文章目录
- 1. 引言
- 2. 进程&线程——概念
- 3. 进程控制块/进程描述符(PCB)
- 4. 进程内核栈(Kernel Stack)
- 4.1. 进程内核栈的定义
- 4.2. thread_info 体系结构相关进程描述
- 4.3. 定位进程描述符(task_struct)和内核栈以及内核栈指针的问题
- 5. 进程 ID(PID)
- 6. 进程状态
- 6.1. 进程的几个状态
- 6.2. 设置进程状态
- 设置当前状态
- 设置特殊状态
- 6.3. 进程状态检查
- 6.4. 状态切换的几个核心函数
- 6.5. 经典的进程状态转换
- #附
- #01. 计算 task_struct 大小
- #01.1. 编写 task_struct_size 模块
- #01.2. 编写 Makefile
- #01.3. 编译并插入模块
- #01.4. 查看内核打印
- #02. 验证内核栈大小
- #03. 验证内核栈结构
- #04. 验证栈指针位置
- #下一篇
1. 引言
进程是 Linux
操作系统抽象概念中最基本的一种。本文将和读者一起学习有关 Linux
内核中对进程、线程的概念;然后再一起研究在 Linux
内核中是如何管理每个进程,本文只涉及对进程以及线程生命周期的介绍(进程是如何创建、消亡)。我们的操作系统存在的目的就是为了更好的运行用户程序,由此可见进程管理在操作系统中的地位可谓是至关重要的。而有关进程调度内容笔者将放在下一篇文章中详述,虽然这些都属于进程管理的范畴,不过由于笔者是按照《Linux 内核设计与实现》第三版并结合 Linux 6.15.0-rc2
内核代码来展开讨论,因此这里的探讨顺序将会与书籍原文保持一致。但是由于加上较为细节的分析内核代买,笔者写完发现这章内容巨多,因此将分为多篇内容发布,也方便读者们能够轻松看完。
2. 进程&线程——概念
在计算机科学领域,有关进程(Process
)较为官方的定义是,进程是“正在执行的程序实例”。与静态存储在磁盘上的程序文件不同,进程具有动态的执行上下文,包括程序计数器(PC
)、寄存器集、堆栈以及进程控制块(PCB
)中记录的资源分配信息。与之对应衍生出的概念线程(Thread
),较为官方的定义是,线程是“正在执行的程序指令序列中的最小可独立调度单元”。它包含程序计数器、寄存器状态、堆栈以及线程控制块(TCB
)等上下文信息。线程也被称为轻量级进程,因为它与同属一个进程的其他线程共享同一地址空间和打开的文件描述符等资源。
[注]:这里笔者从 Wiki 偷过来一张图供读者们稍微形象的理解——此处为照顾之前没有学习过操作系统的同学们。
事实上,进程就是正在执行的程序代码的实时结果。内核的调度对象是线程,而非进程。在 Linux
内核中事实上并不特别区分进程和线程。对于 Linux
而言线程只不过是一种特殊的进程而已。进程的另一个名字也叫 task
,Linux
内核也通常把进程叫做任务(task
),文中提到的任务、进程所指的都是进程。
3. 进程控制块/进程描述符(PCB)
操作系统中用于记录和管理每个进程所有信息的结构称为进程控制块(Process Control Block
,简称 PCB
)。在 Linux
系统中这种结构也会被称为进程描述符(Process Descriptor
)。Linux
内核中将进程列表存放在一个任务队列(task list
)的双向循环链表中。链表中的每一项类型为 task_struct
结构体类型,这个类型也就是 Linux
内核中的进程描述符,在该结构体中保存了一个操作系统中进程的所有通用信息。
 task_struct
结构体相对比较大,在笔者当前的 64
位机器下,该结构体大约占用 13.43KB
。#01(数据的计算过程见文末附 #01
)由于现代计算机对性能要求越来越高,因此现代操作系统体积越来越庞大,用于管理进程的数据结构保存的信息也越来越多,因此进程描述符的体积也逐渐增加。
task_struct
结构体中的成员非常多,现在就暂且看看就行,等真正遇见再回过头来仔细分析其含义和作用。
// linux 6.15.0-rc2
// PATH: include/linux/sched.h
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK/** For reasons of header soup (see current_thread_info()), this* must be the first element of task_struct.*/struct thread_info thread_info;
#endifunsigned int __state;/* saved state for "spinlock sleepers" */unsigned int saved_state;/** This begins the randomizable portion of task_struct. Only* scheduling-critical items should be added above here.*/randomized_struct_fields_startvoid *stack;refcount_t usage;/* Per task flags (PF_*), defined further below: */unsigned int flags;unsigned int ptrace;#ifdef CONFIG_MEM_ALLOC_PROFILINGstruct alloc_tag *alloc_tag;
#endif#ifdef CONFIG_SMPint 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;
#endifint 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;
#ifdef CONFIG_SCHED_CLASS_EXTstruct sched_ext_entity scx;
#endifconst struct sched_class *sched_class;#ifdef CONFIG_SCHED_COREstruct rb_node core_node;unsigned long core_cookie;unsigned int core_occupation;
#endif#ifdef CONFIG_CGROUP_SCHEDstruct task_group *sched_task_group;
#endif#ifdef CONFIG_UCLAMP_TASK/** Clamp values requested for a scheduling entity.* Must be updated with task_rq_lock() held.*/struct uclamp_se uclamp_req[UCLAMP_CNT];/** Effective clamp values used for a scheduling entity.* Must be updated with task_rq_lock() held.*/struct uclamp_se uclamp[UCLAMP_CNT];
#endifstruct sched_statistics stats;#ifdef CONFIG_PREEMPT_NOTIFIERS/* List of struct preempt_notifier: */struct hlist_head preempt_notifiers;
#endif#ifdef CONFIG_BLK_DEV_IO_TRACEunsigned int btrace_seq;
#endifunsigned int policy;unsigned long max_allowed_capacity;int nr_cpus_allowed;const cpumask_t *cpus_ptr;cpumask_t *user_cpus_ptr;cpumask_t cpus_mask;void *migration_pending;
#ifdef CONFIG_SMPunsigned short migration_disabled;
#endifunsigned short migration_flags;#ifdef CONFIG_PREEMPT_RCUint rcu_read_lock_nesting;union rcu_special rcu_read_unlock_special;struct list_head rcu_node_entry;struct rcu_node *rcu_blocked_node;
#endif /* #ifdef CONFIG_PREEMPT_RCU */#ifdef CONFIG_TASKS_RCUunsigned long rcu_tasks_nvcsw;u8 rcu_tasks_holdout;u8 rcu_tasks_idx;int rcu_tasks_idle_cpu;struct list_head rcu_tasks_holdout_list;int rcu_tasks_exit_cpu;struct list_head rcu_tasks_exit_list;
#endif /* #ifdef CONFIG_TASKS_RCU */#ifdef CONFIG_TASKS_TRACE_RCUint trc_reader_nesting;int trc_ipi_to_cpu;union rcu_special trc_reader_special;struct list_head trc_holdout_list;struct list_head trc_blkd_node;int trc_blkd_cpu;
#endif /* #ifdef CONFIG_TASKS_TRACE_RCU */struct sched_info sched_info;struct list_head tasks;
#ifdef CONFIG_SMPstruct plist_node pushable_tasks;struct rb_node pushable_dl_tasks;
#endifstruct mm_struct *mm;struct mm_struct *active_mm;struct address_space *faults_disabled_mapping;int exit_state;int exit_code;int exit_signal;/* The signal sent when the parent dies: */int pdeath_signal;/* JOBCTL_*, siglock protected: */unsigned long jobctl;/* Used for emulating ABI behavior of previous Linux versions: */unsigned int personality;/* Scheduler bits, serialized by scheduler locks: */unsigned sched_reset_on_fork:1;unsigned sched_contributes_to_load:1;unsigned sched_migrated:1;unsigned sched_task_hot:1;/* Force alignment to the next boundary: */unsigned :0;/* Unserialized, strictly 'current' *//** This field must not be in the scheduler word above due to wakelist* queueing no longer being serialized by p->on_cpu. However:** p->XXX = X; ttwu()* schedule() if (p->on_rq && ..) // false* smp_mb__after_spinlock(); if (smp_load_acquire(&p->on_cpu) && //true* deactivate_task() ttwu_queue_wakelist())* p->on_rq = 0; p->sched_remote_wakeup = Y;** guarantees all stores of 'current' are visible before* ->sched_remote_wakeup gets used, so it can be in this word.*/unsigned sched_remote_wakeup:1;
#ifdef CONFIG_RT_MUTEXESunsigned sched_rt_mutex:1;
#endif/* Bit to tell TOMOYO we're in execve(): */unsigned in_execve:1;unsigned in_iowait:1;
#ifndef TIF_RESTORE_SIGMASKunsigned restore_sigmask:1;
#endif
#ifdef CONFIG_MEMCG_V1unsigned in_user_fault:1;
#endif
#ifdef CONFIG_LRU_GEN/* whether the LRU algorithm may apply to this access */unsigned in_lru_fault:1;
#endif
#ifdef CONFIG_COMPAT_BRKunsigned brk_randomized:1;
#endif
#ifdef CONFIG_CGROUPS/* disallow userland-initiated cgroup migration */unsigned no_cgroup_migration:1;/* task is frozen/stopped (used by the cgroup freezer) */unsigned frozen:1;
#endif
#ifdef CONFIG_BLK_CGROUPunsigned use_memdelay:1;
#endif
#ifdef CONFIG_PSI/* Stalled due to lack of memory */unsigned in_memstall:1;
#endif
#ifdef CONFIG_PAGE_OWNER/* Used by page_owner=on to detect recursion in page tracking. */unsigned in_page_owner:1;
#endif
#ifdef CONFIG_EVENTFD/* Recursion prevention for eventfd_signal() */unsigned in_eventfd:1;
#endif
#ifdef CONFIG_ARCH_HAS_CPU_PASIDunsigned pasid_activated:1;
#endif
#ifdef CONFIG_X86_BUS_LOCK_DETECTunsigned reported_split_lock:1;
#endif
#ifdef CONFIG_TASK_DELAY_ACCT/* delay due to memory thrashing */unsigned in_thrashing:1;
#endif
#ifdef CONFIG_PREEMPT_RTstruct netdev_xmit net_xmit;
#endifunsigned long atomic_flags; /* Flags requiring atomic access. */struct restart_block restart_block;pid_t pid;pid_t tgid;#ifdef CONFIG_STACKPROTECTOR/* Canary value for the -fstack-protector GCC feature: */unsigned long stack_canary;
#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;/** 'ptraced' is the list of tasks this task is using ptrace() on.** This includes both natural children and PTRACE_ATTACH targets.* 'ptrace_entry' is this task's link on the p->parent->ptraced list.*/struct list_head ptraced;struct list_head ptrace_entry;/* PID/PID hash table linkage. */struct pid *thread_pid;struct hlist_node pid_links[PIDTYPE_MAX];struct list_head thread_node;struct completion *vfork_done;/* CLONE_CHILD_SETTID: */int __user *set_child_tid;/* CLONE_CHILD_CLEARTID: */int __user *clear_child_tid;/* PF_KTHREAD | PF_IO_WORKER */void *worker_private;u64 utime;u64 stime;
#ifdef CONFIG_ARCH_HAS_SCALED_CPUTIMEu64 utimescaled;u64 stimescaled;
#endifu64 gtime;struct prev_cputime prev_cputime;
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GENstruct vtime vtime;
#endif#ifdef CONFIG_NO_HZ_FULLatomic_t tick_dep_mask;
#endif/* Context switch counts: */unsigned long nvcsw;unsigned long nivcsw;/* Monotonic time in nsecs: */u64 start_time;/* Boot based time in nsecs: */u64 start_boottime;/* MM fault and swap info: this can arguably be seen as either mm-specific or thread-specific: */unsigned long min_flt;unsigned long maj_flt;/* Empty if CONFIG_POSIX_CPUTIMERS=n */struct posix_cputimers posix_cputimers;#ifdef CONFIG_POSIX_CPU_TIMERS_TASK_WORKstruct posix_cputimers_work posix_cputimers_work;
#endif/* Process credentials: *//* Tracer's credentials at attach: */const struct cred __rcu *ptracer_cred;/* Objective and real subjective task credentials (COW): */const struct cred __rcu *real_cred;/* Effective (overridable) subjective task credentials (COW): */const struct cred __rcu *cred;#ifdef CONFIG_KEYS/* Cached requested key. */struct key *cached_requested_key;
#endif/** executable name, excluding path.** - normally initialized begin_new_exec()* - set it with set_task_comm()* - strscpy_pad() to ensure it is always NUL-terminated and* zero-padded* - task_lock() to ensure the operation is atomic and the name is* fully updated.*/char comm[TASK_COMM_LEN];struct nameidata *nameidata;#ifdef CONFIG_SYSVIPCstruct sysv_sem sysvsem;struct sysv_shm sysvshm;
#endif
#ifdef CONFIG_DETECT_HUNG_TASKunsigned long last_switch_count;unsigned long last_switch_time;
#endif/* Filesystem information: */struct fs_struct *fs;/* Open file information: */struct files_struct *files;#ifdef CONFIG_IO_URINGstruct io_uring_task *io_uring;
#endif/* Namespaces: */struct nsproxy *nsproxy;/* Signal handlers: */struct signal_struct *signal;struct sighand_struct __rcu *sighand;sigset_t blocked;sigset_t real_blocked;/* Restored if set_restore_sigmask() was used: */sigset_t saved_sigmask;struct sigpending pending;unsigned long sas_ss_sp;size_t sas_ss_size;unsigned int sas_ss_flags;struct callback_head *task_works;#ifdef CONFIG_AUDIT
#ifdef CONFIG_AUDITSYSCALLstruct audit_context *audit_context;
#endifkuid_t loginuid;unsigned int sessionid;
#endifstruct seccomp seccomp;struct syscall_user_dispatch syscall_dispatch;/* Thread group tracking: */u64 parent_exec_id;u64 self_exec_id;/* Protection against (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed, mempolicy: */spinlock_t alloc_lock;/* Protection of the PI data structures: */raw_spinlock_t pi_lock;struct wake_q_node wake_q;#ifdef CONFIG_RT_MUTEXES/* PI waiters blocked on a rt_mutex held by this task: */struct rb_root_cached pi_waiters;/* Updated under owner's pi_lock and rq lock */struct task_struct *pi_top_task;/* Deadlock detection and priority inheritance handling: */struct rt_mutex_waiter *pi_blocked_on;
#endif#ifdef CONFIG_DEBUG_MUTEXES/* Mutex deadlock detection: */struct mutex_waiter *blocked_on;
#endif#ifdef CONFIG_DETECT_HUNG_TASK_BLOCKERstruct mutex *blocker_mutex;
#endif#ifdef CONFIG_DEBUG_ATOMIC_SLEEPint non_block_count;
#endif#ifdef CONFIG_TRACE_IRQFLAGSstruct irqtrace_events irqtrace;unsigned int hardirq_threaded;u64 hardirq_chain_key;int softirqs_enabled;int softirq_context;int irq_config;
#endif
#ifdef CONFIG_PREEMPT_RTint softirq_disable_cnt;
#endif#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 48ULu64 curr_chain_key;int lockdep_depth;unsigned int lockdep_recursion;struct held_lock held_locks[MAX_LOCK_DEPTH];
#endif#if defined(CONFIG_UBSAN) && !defined(CONFIG_UBSAN_TRAP)unsigned int in_ubsan;
#endif/* Journalling filesystem info: */void *journal_info;/* Stacked block device info: */struct bio_list *bio_list;/* Stack plugging: */struct blk_plug *plug;/* VM state: */struct reclaim_state *reclaim_state;struct io_context *io_context;#ifdef CONFIG_COMPACTIONstruct capture_control *capture_control;
#endif/* Ptrace state: */unsigned long ptrace_message;kernel_siginfo_t *last_siginfo;struct task_io_accounting ioac;
#ifdef CONFIG_PSI/* Pressure stall state */unsigned int psi_flags;
#endif
#ifdef CONFIG_TASK_XACCT/* Accumulated RSS usage: */u64 acct_rss_mem1;/* Accumulated virtual memory usage: */u64 acct_vm_mem1;/* stime + utime since last update: */u64 acct_timexpd;
#endif
#ifdef CONFIG_CPUSETS/* Protected by ->alloc_lock: */nodemask_t mems_allowed;/* Sequence number to catch updates: */seqcount_spinlock_t mems_allowed_seq;int cpuset_mem_spread_rotor;
#endif
#ifdef CONFIG_CGROUPS/* Control Group info protected by css_set_lock: */struct css_set __rcu *cgroups;/* cg_list protected by css_set_lock and tsk->alloc_lock: */struct list_head cg_list;
#endif
#ifdef CONFIG_X86_CPU_RESCTRLu32 closid;u32 rmid;
#endif
#ifdef CONFIG_FUTEXstruct robust_list_head __user *robust_list;
#ifdef CONFIG_COMPATstruct compat_robust_list_head __user *compat_robust_list;
#endifstruct list_head pi_state_list;struct futex_pi_state *pi_state_cache;struct mutex futex_exit_mutex;unsigned int futex_state;
#endif
#ifdef CONFIG_PERF_EVENTSu8 perf_recursion[PERF_NR_CONTEXTS];struct perf_event_context *perf_event_ctxp;struct mutex perf_event_mutex;struct list_head perf_event_list;struct perf_ctx_data __rcu *perf_ctx_data;
#endif
#ifdef CONFIG_DEBUG_PREEMPTunsigned long preempt_disable_ip;
#endif
#ifdef CONFIG_NUMA/* Protected by alloc_lock: */struct mempolicy *mempolicy;short il_prev;u8 il_weight;short pref_node_fork;
#endif
#ifdef CONFIG_NUMA_BALANCINGint numa_scan_seq;unsigned int numa_scan_period;unsigned int numa_scan_period_max;int numa_preferred_nid;unsigned long numa_migrate_retry;/* Migration stamp: */u64 node_stamp;u64 last_task_numa_placement;u64 last_sum_exec_runtime;struct callback_head numa_work;/** This pointer is only modified for current in syscall and* pagefault context (and for tasks being destroyed), so it can be read* from any of the following contexts:* - RCU read-side critical section* - current->numa_group from everywhere* - task's runqueue locked, task not running*/struct numa_group __rcu *numa_group;/** numa_faults is an array split into four regions:* faults_memory, faults_cpu, faults_memory_buffer, faults_cpu_buffer* in this precise order.** faults_memory: Exponential decaying average of faults on a per-node* basis. Scheduling placement decisions are made based on these* counts. The values remain static for the duration of a PTE scan.* faults_cpu: Track the nodes the process was running on when a NUMA* hinting fault was incurred.* faults_memory_buffer and faults_cpu_buffer: Record faults per node* during the current scan window. When the scan completes, the counts* in faults_memory and faults_cpu decay and these values are copied.*/unsigned long *numa_faults;unsigned long total_numa_faults;/** numa_faults_locality tracks if faults recorded during the last* scan window were remote/local or failed to migrate. The task scan* period is adapted based on the locality of the faults with different* weights depending on whether they were shared or private faults*/unsigned long numa_faults_locality[3];unsigned long numa_pages_migrated;
#endif /* CONFIG_NUMA_BALANCING */#ifdef CONFIG_RSEQstruct rseq __user *rseq;u32 rseq_len;u32 rseq_sig;/** RmW on rseq_event_mask must be performed atomically* with respect to preemption.*/unsigned long rseq_event_mask;
# ifdef CONFIG_DEBUG_RSEQ/** This is a place holder to save a copy of the rseq fields for* validation of read-only fields. The struct rseq has a* variable-length array at the end, so it cannot be used* directly. Reserve a size large enough for the known fields.*/char rseq_fields[sizeof(struct rseq)];
# endif
#endif#ifdef CONFIG_SCHED_MM_CIDint mm_cid; /* Current cid in mm */int last_mm_cid; /* Most recent cid in mm */int migrate_from_cpu;int mm_cid_active; /* Whether cid bitmap is active */struct callback_head cid_work;
#endifstruct tlbflush_unmap_batch tlb_ubc;/* Cache last used pipe for splice(): */struct pipe_inode_info *splice_pipe;struct page_frag task_frag;#ifdef CONFIG_TASK_DELAY_ACCTstruct task_delay_info *delays;
#endif#ifdef CONFIG_FAULT_INJECTIONint make_it_fail;unsigned int fail_nth;
#endif/** When (nr_dirtied >= nr_dirtied_pause), it's time to call* balance_dirty_pages() for a dirty throttling pause:*/int nr_dirtied;int nr_dirtied_pause;/* Start of a write-and-pause period: */unsigned long dirty_paused_when;#ifdef CONFIG_LATENCYTOPint latency_record_count;struct latency_record latency_record[LT_SAVECOUNT];
#endif/** Time slack values; these are used to round up poll() and* select() etc timeout values. These are in nanoseconds.*/u64 timer_slack_ns;u64 default_timer_slack_ns;#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)unsigned int kasan_depth;
#endif#ifdef CONFIG_KCSANstruct kcsan_ctx kcsan_ctx;
#ifdef CONFIG_TRACE_IRQFLAGSstruct irqtrace_events kcsan_save_irqtrace;
#endif
#ifdef CONFIG_KCSAN_WEAK_MEMORYint kcsan_stack_depth;
#endif
#endif#ifdef CONFIG_KMSANstruct kmsan_ctx kmsan_ctx;
#endif#if IS_ENABLED(CONFIG_KUNIT)struct kunit *kunit_test;
#endif#ifdef CONFIG_FUNCTION_GRAPH_TRACER/* Index of current stored address in ret_stack: */int curr_ret_stack;int curr_ret_depth;/* Stack of return addresses for return function tracing: */unsigned long *ret_stack;/* Timestamp for last schedule: */unsigned long long ftrace_timestamp;unsigned long long ftrace_sleeptime;/** Number of functions that haven't been traced* because of depth overrun:*/atomic_t trace_overrun;/* Pause tracing: */atomic_t tracing_graph_pause;
#endif#ifdef CONFIG_TRACING/* Bitmask and counter of trace recursion: */unsigned long trace_recursion;
#endif /* CONFIG_TRACING */#ifdef CONFIG_KCOV/* See kernel/kcov.c for more details. *//* Coverage collection mode enabled for this task (0 if disabled): */unsigned int kcov_mode;/* Size of the kcov_area: */unsigned int kcov_size;/* Buffer for coverage collection: */void *kcov_area;/* KCOV descriptor wired with this task or NULL: */struct kcov *kcov;/* KCOV common handle for remote coverage collection: */u64 kcov_handle;/* KCOV sequence number: */int kcov_sequence;/* Collect coverage from softirq context: */unsigned int kcov_softirq;
#endif#ifdef CONFIG_MEMCG_V1struct mem_cgroup *memcg_in_oom;
#endif#ifdef CONFIG_MEMCG/* Number of pages to reclaim on returning to userland: */unsigned int memcg_nr_pages_over_high;/* Used by memcontrol for targeted memcg charge: */struct mem_cgroup *active_memcg;/* Cache for current->cgroups->memcg->objcg lookups: */struct obj_cgroup *objcg;
#endif#ifdef CONFIG_BLK_CGROUPstruct gendisk *throttle_disk;
#endif#ifdef CONFIG_UPROBESstruct uprobe_task *utask;
#endif
#if defined(CONFIG_BCACHE) || defined(CONFIG_BCACHE_MODULE)unsigned int sequential_io;unsigned int sequential_io_avg;
#endifstruct kmap_ctrl kmap_ctrl;
#ifdef CONFIG_DEBUG_ATOMIC_SLEEPunsigned long task_state_change;
# ifdef CONFIG_PREEMPT_RTunsigned long saved_state_change;
# endif
#endifstruct rcu_head rcu;refcount_t rcu_users;int pagefault_disabled;
#ifdef CONFIG_MMUstruct task_struct *oom_reaper_list;struct timer_list oom_reaper_timer;
#endif
#ifdef CONFIG_VMAP_STACKstruct vm_struct *stack_vm_area;
#endif
#ifdef CONFIG_THREAD_INFO_IN_TASK/* A live task holds one reference: */refcount_t stack_refcount;
#endif
#ifdef CONFIG_LIVEPATCHint patch_state;
#endif
#ifdef CONFIG_SECURITY/* Used by LSM modules for access restriction: */void *security;
#endif
#ifdef CONFIG_BPF_SYSCALL/* Used by BPF task local storage */struct bpf_local_storage __rcu *bpf_storage;/* Used for BPF run context */struct bpf_run_ctx *bpf_ctx;
#endif/* Used by BPF for per-TASK xdp storage */struct bpf_net_context *bpf_net_context;#ifdef CONFIG_GCC_PLUGIN_STACKLEAKunsigned long lowest_stack;unsigned long prev_lowest_stack;
#endif#ifdef CONFIG_X86_MCEvoid __user *mce_vaddr;__u64 mce_kflags;u64 mce_addr;__u64 mce_ripv : 1,mce_whole_page : 1,__mce_reserved : 62;struct callback_head mce_kill_me;int mce_count;
#endif#ifdef CONFIG_KRETPROBESstruct llist_head kretprobe_instances;
#endif
#ifdef CONFIG_RETHOOKstruct llist_head rethooks;
#endif#ifdef CONFIG_ARCH_HAS_PARANOID_L1D_FLUSH/** If L1D flush is supported on mm context switch* then we use this callback head to queue kill work* to kill tasks that are not running on SMT disabled* cores*/struct callback_head l1d_flush_kill;
#endif#ifdef CONFIG_RV/** Per-task RV monitor. Nowadays fixed in RV_PER_TASK_MONITORS.* If we find justification for more monitors, we can think* about adding more or developing a dynamic method. So far,* none of these are justified.*/union rv_task_monitor rv[RV_PER_TASK_MONITORS];
#endif#ifdef CONFIG_USER_EVENTSstruct user_event_mm *user_event_mm;
#endif/** New fields for task_struct should be added above here, so that* they are included in the randomized portion of task_struct.*/randomized_struct_fields_end/* CPU-specific state of this task: */struct thread_struct thread;/** WARNING: on x86, 'thread_struct' contains a variable-sized* structure. It *MUST* be at the end of 'task_struct'.** Do not put anything below here!*/
};
4. 进程内核栈(Kernel Stack)
每个进程最终都会通过系统调用陷入内核执行,进程内核栈用于在执行系统调用、异常或中断处理时保存上下文。在内核态运行的进程所使用的栈会与用户空间的栈不同,是一个在单独在内核空间的栈,也被称作内核栈。进程内核栈在进程创建的时候调用 alloc_thread_stack_node
函数最终由 __slab_alloc_node
函数从 slab
缓存池中分配内存,其大小为 THREAD_SIZE
。从历史上说,通常来讲内核栈的大小是两页,也就是在 32
位机器上的内核栈就是 8KB
,64
位机器上的内核栈就是 16KB
,是一个固定不变的值。
[注]:有关内核栈分配与管理的相关内容笔者将在后面的文章中继续与大家一同学习,本文暂不涉及改内容。
4.1. 进程内核栈的定义
内核栈的定义如下,内核栈在理论上被定义为一个 union
,该结构被称之为联合体或者共用体,该类型的特性是 “一个联合体的大小等于其内部所占空间最大的成员大小,并且所有成员都共享同一段内存”。根据这个特性,我们也就知道了一个内核栈 thread_union
的大小就是 sizeof(unsigned long) * (THREAD_SIZE / sizeof(long)) = THERAD_SIZE
。
// Linux Kernel 2.6.34
// PATH: include/linux/sched.h
union thread_union {struct thread_info thread_info;unsigned long stack[THREAD_SIZE/sizeof(long)];
};//--------------------------------------------------------------------// Linux Kernel 6.15.0-rc2
// PATH: include/linux/sched.h
union thread_union {struct task_struct task;
#ifndef CONFIG_THREAD_INFO_IN_TASKstruct thread_info thread_info;
#endifunsigned long stack[THREAD_SIZE/sizeof(long)];
};
为什么这里笔者要强调 “理论上” 一词,这是由于在以往较旧版本的内核中的确是这样做的,在网络上有很多博客和文章都在介绍内核栈也是用的较老版本的内核代码,因此他们的说法也是对的。较老版本的内核中的内核栈是将 thread_info
与 真正的内核栈 stack
绑定在一起通过 alloc_thread_info
分配两个页面的内存空间。因此,这时候的 thread_info
地址就是内核栈的内存起始地址。(在旧版本的内核中) 而在 thread_info
中定义了指向进程描述符 struct_task
的指针,以此来寻找进程描述符。
// Linux Kernel 2.6.34
// PATH: arch/x86/include/asm/thread_info.h
#define alloc_thread_info(tsk) \((struct thread_info *)__get_free_pages(THREAD_FLAGS, THREAD_ORDER))
__get_free_pages
是一个内核函数,用于分配连续的物理内存页。THREAD_ORDER
决定了内核栈的大小(通常为2
个页)。- 分配的内存同时包含
thread_info
和内核栈,thread_info
位于内核栈的底部。
因此,旧版本的内核栈就看起来像如下图这两种都是对的。
[注]:x86 的栈是向下生长的,即从高地址向低地址扩展。
在新版本中的内核代码中 thread_union
的定义仍然存在,但它很少被直接使用。这是因为内核的设计已经逐渐从直接依赖 thread_union
转向更灵活的方式来管理任务的内核栈和相关数据结构,例如通过 task_struct
和独立的栈分配机制,在 dup_task_struct
中使用 alloc_thread_stack_node
来为 task_struct
分配内存空间,使用 alloc_thread_stack_node
为内核栈分配内存空间。因此新版本的内核栈看起来就像是这样。也可以简单的认为内核栈在逻辑上就是一片连续的内存空间。
内核栈空间是否物理连续取决于
CONFIG_VMAP_STACK
是否开启,开启则内核栈空间最终由__vmalloc_node
使用虚拟内存分配,虽然虚拟地址是连续的,但物理上可能是分散的。反之则最终通过alloc_pages_node
分配的内存是物理连续的。(一般情况,该配置是默认开启。)
[注]:这里实际上已经和 thread_union 没什么太大关系了。
由于笔者是在
x86_64
的机器下编写本文。因此这里主要就以x86_64
架构为主,不同架构在arch
目录下的代码会有不同,日后有余力在将其整理对比。下面就来看看THREAD_SIZE
到底是多少。
找到 x86
架构下的 THREAD_SIZE
定义位置,如下。可以看到 THREAD_SIZE
是通过 PAGE_SIZE
来计算的。在 32
位下,直接就等于 PAGE_SIZE * 2
,而在 64
位下则需要判断 CONFIG_KASAN
,然后再进行计算。
// Linux Kernel 6.15.0-rc2
// PATH: arch/x86/include/asm/page_32_types.h
#define THREAD_SIZE_ORDER 1
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)//--------------------------------------------------------------// Linux Kernel 6.15.0-rc2
// PATH: arch/x86/include/asm/page_64_types.h
#ifdef CONFIG_KASAN
#define KASAN_STACK_ORDER 1
#else
#define KASAN_STACK_ORDER 0
#endif#define THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER)
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
再来找到 PAGE_SIZE
的定义位置,直接就是通过 1 << CONFIG_PAGE_SHIFT
这个值来得到最终页面大小。
// Linux Kernel 6.15.0-rc2
// PATH: include/vdso/page.h
#define PAGE_SHIFT CONFIG_PAGE_SHIFT#define PAGE_SIZE (_AC(1,UL) << CONFIG_PAGE_SHIFT)
通过查看编译内核时的 .config
文件就可以这两个未确定的值是多少,那么这里从而也就确定了。其实此时的 PAGE_SIZE
都是 1 << 12 = 4096 = 4KB
,这里可以看到在 32
位机器上,那么 THREAD_SIZE = 2 * 4KB = 8KB
。而在 64
位机器上,就是 THREAD_SIZE = 2^2 * 4KB = 16KB
。#02
[注]:具体验证计算结果的过程可见文末附 #02 部分内容。
4.2. thread_info 体系结构相关进程描述
在上面小节中内核栈的定义代码里我们可以看到除了 task_struct
类型,还有一个不可忽视的 thread_info
类型。通过代码了解到 thread_info
在 thread_union
和 task_struct
中都可能包含。
[注]:虽然笔者已经提前告知各位 thread_union 类型几乎没什么作用了,不过为了研究透彻,这里我们还是抱着学习的心态一起探个究竟。
// Linux Kernel 2.6.34
// PATH: arch/x86/include/asm/thread_info.h
struct thread_info {struct task_struct *task; /* main task structure */struct exec_domain *exec_domain; /* execution domain */__u32 flags; /* low level flags */__u32 status; /* thread synchronous flags */__u32 cpu; /* current CPU */int preempt_count; /* 0 => preemptable,<0 => BUG */mm_segment_t addr_limit;struct restart_block restart_block;void __user *sysenter_return;
#ifdef CONFIG_X86_32unsigned long previous_esp; /* ESP of the previous stack incase of nested (IRQ) stacks*/__u8 supervisor_stack[0];
#endifint uaccess_err;
};//--------------------------------------------------------------------// Linux Kernel 6.15.0-rc2
// PATH: arch/x86/include/asm/thread_info.h
struct thread_info {unsigned long flags; /* low level flags */unsigned long syscall_work; /* SYSCALL_WORK_ flags */u32 status; /* thread synchronous flags */
#ifdef CONFIG_SMPu32 cpu; /* current CPU */
#endif
};
实际上在 linux kernel
中,task_struct
、thread_info
都用来保存进程相关信息,即进程 PCB(Process Control Block)
信息。然而不同的体系结构里,进程需要存储的信息不尽相同,因此 linux
将其分为两部分存储,使用 task_struct
存储进程通用的信息,将体系结构相关的部分存储在 thread_info
中。这也是为什么 struct task_struct
在 include/linux/sched.h
中定义,而 thread_info
在 arch/
下体系结构相关头文件里定义的原因。下面具体看看 thread_info
到底会被包含在哪个类型中。
在代码中以及 .config
文件中我们了解到,CONFIG_THREAD_INFO_IN_TASK
这个宏有定义(事实上这个宏 CONFIG_THREAD_INFO_IN_TASK
在新版内核 x86_64
中是默认开启的),那么当前内核就是直接把 thread_info
放进 task_struct
里,则 thread_union
中不再包含,反之则反。也就是说进程内核栈 thread_union
的首地址就是 task_struct
的首地址,也就是 task_struct
的第一个成员 thread_info
的地址。 那么整个内核栈空间目前就看起来像下图这样。
[注]:请注意标黄这段分析,这是在假设现在还不清楚内核是否不再使用 thread_union 的情况。这里假设使用的就是 thread_union,则符合此分析结果。
[注]:x86 的栈是向下生长的,即从高地址向低地址扩展。
4.3. 定位进程描述符(task_struct)和内核栈以及内核栈指针的问题
通过以上内容我们已经了解到了一个内核栈的具体样貌。接下来一起来学习如何定位一个内核栈和进程描述符(task_struct
也就是 PCB
),或者说是,用代码如何找到进程的内核栈(kernel stack
)以及一个进程的进程描述符(task_struct
)。
通过上文我们已经知道了有一个配置宏是默认开启的,那就是 CONFIG_THREAD_INFO_IN_TASK
,与它相关的代码还有下面这部分。
// Linux Kernel 6.15.0-rc2
// PATH: include/linux/thread_info.h
#ifdef CONFIG_THREAD_INFO_IN_TASK
/** For CONFIG_THREAD_INFO_IN_TASK kernels we need <asm/current.h> for the* definition of current, but for !CONFIG_THREAD_INFO_IN_TASK kernels,* including <asm/current.h> can cause a circular dependency on some platforms.*/
#include <asm/current.h>
#define current_thread_info() ((struct thread_info *)current)
#endif
其实通过函数名字就可以才得到它的意图,就是获取 thread_info
,而 thread_info
的首地址也就等于 task_struct
首地址。无论是通过所包含的头文件,还是通过注释内容我们都知道,这里用来定义宏的 current
应该去体系结构相关的头文件下去找 current.h
。
// Linux Kernel 6.15.0-rc2
// arch/x86/include/asm/current.h
struct task_struct;DECLARE_PER_CPU_CACHE_HOT(struct task_struct *, current_task);
/* const-qualified alias provided by the linker. */
DECLARE_PER_CPU_CACHE_HOT(struct task_struct * const __percpu_seg_override,const_current_task);static __always_inline struct task_struct *get_current(void)
{if (IS_ENABLED(CONFIG_USE_X86_SEG_SUPPORT))return this_cpu_read_const(const_current_task);return this_cpu_read_stable(current_task);
}#define current get_current()
那么这里就来到了 x86
架构下的 current.h
文件,current
是一个宏定义,最终调用的是 get_current
函数来获取当前函数的 task_struct
指针。
get_current
函数涉及的内容较多,这里简单介绍一下get_currnet
函数的工作原理。
这里使用到了内核的一个per-CPU
机制来获取当前进程的task_struct
。per-CPU
变量是一种特殊的变量类型,每个CPU
都有自己独立的实例。这种机制允许每个CPU
独立访问和操作自己的变量实例。每个 CPU 分配独立存储空间的变量,每个 CPU 都有自己的副本。
- 当前代码中首先用
DECLARE_PER_CPU_CACHE_HOT
声明了一个per-CPU
变量,类型为struct task_struct *
;this_cpu_read_stable
是一个内核宏,用于读取当前CPU
上per-CPU
变量的current_task
值并以struct task_struct *
类型返回。到此就完成了读取当前进程
task_struct
的操作。
Linux
内核引入per-CPU
变量之后,逐渐通过per-CPU
变量来实现current
宏,x86
从Linux Kernel 4.1
版本开始逐渐简化thread_info
结构体,直到Linux Kernel 4.9
便彻底移除了thread_info
中的task
,也就不再通过thread_info
获取task_struct
指针了,而改用current_struct percpu
变量存放task_struct
指针,详情可参阅该 [PATCH] x86: Move thread_info into task_struct。
那么这里其实就已经有办法获取当前进程的 task_struct
值了,而根据上面黄标的分析内容,进程描述符 task_struct
的第一个成员就是 thread_info
,而 thread_union
的第一个成员也是 task_struct
,因此实际上在当前内核中,这三个地址是相同的,这里将会是得到一个这样的现象。那么这里用代码来验证一下我们的假设。#03
[72607.977772] task_struct address: ffff88fd9520a940
[72607.977773] task_struct->stack(stack base): ffffca988fdb0000
[72607.977774] task_struct->thread_info address: ffff88fd9520a940
[注]:为了提升本文阅读效果,此处仅展示验证结果,具体过程见文末附 #03 小节内容。
这里发现三个地址并不相同,而只有 task_struct
与 thread_info
是相同的,这是由于 thread_info
就是 task_struct
第一个成员。那么这里也就是验证了其实内核栈地址并不是如同 thread_union
描述的那样。那么通过这里我们更加确信了内核并不是使用 thread_union
这一类型来描述内核栈的,实际上 task_struct
与内核栈是独立分配的,因此在内核栈内存中与 task_struct
的关系应该如下图一般。也可以简单的将内核栈认为是一个逻辑上连续的内存空间。
可以看到上图中笔者还标注了 sp
的位置,这正是我们所熟知的栈指针,而在 task_struct
中的 *stack
所指向的位置并非程序意义上的栈顶或栈底位置,而是是内核栈的内存起始地址。由于 x86
栈的生长方向是向下的,因此栈底位置应该是 task_struct->stack + THREAD_SIZE
,而栈顶的位置保存在 sp
寄存器中,在 x86_64
就是 rsp
寄存器中。以下是验证结果。#04
[73502.218940] task_struct->stack(stack base): ffffca9889dc4000
[73502.218942] task_struct->stack(stack top): ffffca9889de4000
[73502.218944] Current stack pointer (sp): ffffca9889dc77d0
[注]:此处同样仅展示结果内容,具体验证代码见文末附 #04 小节。
5. 进程 ID(PID)
内核通过一个唯一的进程 ID
即 PID(Process identification)
来标识每个进程。内核把每个进程的 PID
存放在他们各自的进程描述符中(task_struct
)的 pid
字段中。
进程 PID
为了与老版本的 Unix
和 Linux
兼容,其最大值默认是 32768(0x8000)
(short int
短整型的最大值),该限制在 <linux/thread.h>
中定义。不过在某些大型系统确实需要更多进程数的话,也可以不考虑兼容问题,直接通过修改 /proc/sys/kernel/pic_max
来提高上限。
// Linux Kernel 2.6.34
// Linux Kernel 6.15.0-rc2
// PATH: linux/include/linux/threads.h
/** This controls the default maximum pid allocated to a process*/
#define PID_MAX_DEFAULT (IS_ENABLED(CONFIG_BASE_SMALL) ? 0x1000 : 0x8000)
6. 进程状态
6.1. 进程的几个状态
进程状态的管理和切换主要通过 task_struct
结构体中的 __state
字段以及相关的状态宏和函数实现。
// Linux Kernel 6.15.0-rc2
// PATH: include/linux/sched.h
/* Used in tsk->__state: */
#define TASK_RUNNING 0x00000000
#define TASK_INTERRUPTIBLE 0x00000001
#define TASK_UNINTERRUPTIBLE 0x00000002
#define __TASK_STOPPED 0x00000004
#define __TASK_TRACED 0x00000008
#define TASK_DEAD 0x00000080
#define TASK_WAKEKILL 0x00000100
#define TASK_WAKING 0x00000200
#define TASK_NOLOAD 0x00000400
#define TASK_NEW 0x00000800
#define TASK_RTLOCK_WAIT 0x00001000
-
TASK_RUNNING:进程为可执行状态(准备运行),或者是正在执行中,或者是在运行队列中等待执行;
-
TASK_INTERRUPTIBLE:进程可中断,表示进程正在阻塞,等待某些条件完成(并且可以被信号中断)。一旦这些条件完成,内核就会把进程设置为运行态。该状态下的进程也可接收信号被提前唤醒准备投入运行,进程在等待时会主动调用
set_current_state(TASK_INTERRUPTIBLE)
; -
TASK_UNINTERRUPTIBLE:进程不可被中断,也就是接收到信号也不会提前唤醒进入运行,而是继续阻塞等待条件完成。适用于需要确保操作完整性的场景(如设备驱动程序中的关键操作)。同时由于此状态的任务对信号不做响应,所以该状态使用的情况较少(此状态下的进程——执行
ps
命令是状态为D
的进程,是无法被kill
命令直接杀死,由于该任务不响应信号。); -
__TASK_STOPPED:进程停止执行,进程没有投入运行,也不能被投入运行。通常这种状态发生在接收到
SIGSTOP
、SIGTSTP
、SIGTTIN
、SIGTTOU
等信号的时候。此外,在调试期间接收到任何信号,都会使进程进入这种状态(进程可以通过SIGCONT
信号恢复运行,状态切换为TASK_RUNNING
); -
__TASK_TRACED:被其它进程跟踪的进程(例如通过
ptrace
、gdb
对调试程序进行跟踪); -
TASK_DEAD:表示进程已经退出,等待被回收。进程在调用
do_exit()
后进入此状态; -
TASK_WAKEKILL:表示进程可以被信号唤醒,即使它处于不可中断的等待状态。通常用于需要强制唤醒的场景;
-
TASK_WAKING:表示进程正在从等待状态切换到运行状态。这是一个过渡状态,通常由调度器内部使用;
-
TASK_NOLOAD:表示进程不会对系统的负载统计产生影响。通常用于内核线程或其他特殊任务;
-
TASK_NEW:表示新创建的任务,尚未被调度运行。通常在任务初始化阶段使用;
-
TASK_RTLOCK_WAIT:表示进程正在等待实时锁(
RT lock
)。这是一个特殊状态,用于实时调度场景; -
TASK_FREEZABLE:表示进程可以被冻结(如在系统挂起时)。进程在冻结时会暂停运行,直到系统恢复;
-
TASK_FROZEN:表示进程已被冻结。通常用于系统挂起或休眠操作;
进程的几个退出状态:
-
EXIT_DEAD:表示进程已经完全退出,等待被回收。与
TASK_DEAD
类似,但用于exit_state
字段; -
EXIT_ZOMBIE:表示进程已退出,但其父进程尚未调用
wait()
回收其状态。进程在此状态下被称为“僵尸进程”; -
TASK_IDLE:表示进程处于空闲状态,不会对系统负载产生影响。通常用于 CPU 的空闲任务。
6.2. 设置进程状态
内核提供了多个宏和函数来设置或修改进程状态:
设置当前状态
// Linux Kernel 6.15.0-rc2
// PATH: include/linux/sched.h
#define __set_current_state(state_value) \do { \debug_normal_state_change((state_value)); \trace_set_current_state(state_value); \WRITE_ONCE(current->__state, (state_value)); \} while (0)#define set_current_state(state_value) \do { \debug_normal_state_change((state_value)); \trace_set_current_state(state_value); \smp_store_mb(current->__state, (state_value)); \} while (0)
__set_current_state
:直接设置当前任务的状态。set_current_state
:在设置状态时添加内存屏障以确保状态的正确性。
设置特殊状态
// Linux Kernel 6.15.0-rc2
// PATH: include/linux/sched.h
#define set_special_state(state_value) \do { \unsigned long flags; \raw_spin_lock_irqsave(¤t->pi_lock, flags);\debug_special_state_change((state_value)); \trace_set_current_state(state_value); \WRITE_ONCE(current->__state, (state_value)); \raw_spin_unlock_irqrestore(¤t->pi_lock, flags);\} while (0)
set_special_state
:用于设置特殊状态(如TASK_DEAD
),并确保与唤醒操作的同步。
6.3. 进程状态检查
通过以下宏检查任务的状态:
// Linux Kernel 6.15.0-rc2
// PATH: include/linux/sched.h
#define task_is_running(task) (READ_ONCE((task)->__state) == TASK_RUNNING)
#define task_is_traced(task) ((READ_ONCE(task->jobctl) & JOBCTL_TRACED) != 0)
#define task_is_stopped(task) ((READ_ONCE(task->jobctl) & JOBCTL_STOPPED) != 0)
6.4. 状态切换的几个核心函数
- schedule()
schedule()
是内核中用于调度任务的核心函数。它会根据任务的状态和优先级选择下一个要运行的任务。
PATH: kernel/sched/core.c
- try_to_wake_up() / wake_up_process()
try_to_wake_up()
用于唤醒处于非运行状态的任务,并将其状态设置为TASK_RUNNING
。(wake_up_process()
内部也是调用try_to_wake_up()
)
PATH: kernel/sched/core.c
- schedule_timeout()
schedule_timeout()
用于在超时后切换任务状态:
PATH: kernel/time/sleep_timeout.c
6.5. 经典的进程状态转换
通过以上这些机制,内核能够高效地管理和切换进程状态。
这里提到了一个进程上下文的概念,上下文简单说来就是一个环境,相对于进程而言,就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。当发生进程调度时,进行进程切换就是上下文切换(context switch
)。操作系统必须对上面提到的进程全部信息进行切换,新调度的进程才能运行。
#附
#01. 计算 task_struct 大小
方法:通过编写一个简单的内核模块,用 sizeof()
去计算 struct task_struct
结构体大小并打印出来。
#01.1. 编写 task_struct_size 模块
编写内核模块文件 task_struct_size.c
。
$ vim task_struct_size.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>static int __init task_struct_size_init(void)
{pr_info("Size of task_struct: %zu bytes\n", sizeof(struct task_struct));return 0;
}static void __exit task_struct_size_exit(void)
{pr_info("Exiting task_struct size module\n");
}module_init(task_struct_size_init);
module_exit(task_struct_size_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Imagine Miracle");
MODULE_DESCRIPTION("Module to calculate task_struct size");
#01.2. 编写 Makefile
编写 Makefile
文件:
obj-m += task_struct_size.oall:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
#01.3. 编译并插入模块
执行 make
编译模块代码,生成 .ko
文件。
$ make
# 编译完成将会生成如下文件
$ ls
Makefile Module.symvers task_struct_size.ko task_struct_size.mod.c task_struct_size.o
modules.order task_struct_size.c task_struct_size.mod task_struct_size.mod.o
插入内核。
$ sudo insmod task_struct_size.ko
# 可以通过 lsmod 命令查看是否插入成功
$ lsmod | grep task_struct_size
task_struct_size 12288 0
#01.4. 查看内核打印
执行 dmesg
命令查看内核输出:
$ sudo dmesg
......
[113194.864193] Size of task_struct: 13760 bytes
一般情况下,最后一行输出就是刚刚插入的内核模块的打印信息,这里可以看到通过 sizeof
所计算出来的 task_struct
大小为 13760 bytes
,约等于 13.43 KB
。
卸载该模块:
$ sudo rmmod task_struct_size
#02. 验证内核栈大小
继续使用 #01
部分的内核模块代码,来添加几行打印:
$ vim task_struct_size.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>static int __init task_struct_size_init(void)
{pr_info("Size of task_struct: %zu bytes\n", sizeof(struct task_struct));pr_info("Size of thread_union: %zu bytes\n", sizeof(union thread_union));pr_info("Size of THREAD_SIZE: %zu bytes\n", THREAD_SIZE);pr_info("Size of PAGE_SIZE: %zu bytes\n", PAGE_SIZE);pr_info("Size of KASAN_STACK_ORDER: %zu bytes\n", KASAN_STACK_ORDER);return 0;
}static void __exit task_struct_size_exit(void)
{pr_info("Exiting task_struct size module\n");
}module_init(task_struct_size_init);
module_exit(task_struct_size_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Imagine Miracle");
MODULE_DESCRIPTION("Module to calculate task_struct size");
插入模块之后,通过 sudo dmesg
查看打印信息如下:
[52373.661330] Size of task_struct: 13760 bytes
[52373.661341] Size of thread_union: 16384 bytes
[52373.661344] Size of THREAD_SIZE: 16384 bytes
[52373.661346] Size of PAGE_SIZE: 4096 bytes
[52373.661349] Size of KASAN_STACK_ORDER: 0 bytes
thread_union
大小与 THREAD_SIZE
相同,为 16348 bytes = 16KB
。
执行 sudo rmmod task_struct_size
移除模块。
$ sudo rmmod task_struct_size
#03. 验证内核栈结构
方法同样还是编写一个简单的内核模块来验证我们的假设。这里是为了验证 task_struct
、thread_info
、thread_union
(内核栈)三个地址是相同的。
编写内核模块文件 task_struct_addr.c
。
$ vim task_struct_addr.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>static int __init print_stack_address_init(void)
{pr_info("task_struct address: %lx \n", (unsigned long **)current);pr_info("task_struct->stack(stack base): %lx \n", (unsigned long **)(current->stack));pr_info("task_struct->thread_info address: %lx \n", (unsigned long *)&(current->thread_info));return 0;
}static void __exit print_stack_address_exit(void)
{pr_info("Exiting module.\n");
}module_init(print_stack_address_init);
module_exit(print_stack_address_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Imagine Miracle");
MODULE_DESCRIPTION("Print kernel stack address.");
编写 Makefile
文件:
obj-m += task_struct_addr.oall:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
执行 make
编译模块,执行 sudo insmod task_struct_addr
插入模块,并通过 sudo dmesg
命令查看内核打印信息。
[72607.977772] task_struct address: ffff88fd9520a940
[72607.977773] task_struct->stack(stack base): ffffca988fdb0000
[72607.977774] task_struct->thread_info address: ffff88fd9520a940
这里发现三个地址并不相同,而只有 task_struct
与 thread_info
是相同的,这是由于 thread_info
就是 task_struct
第一个成员。那么这里也就是验证了其实内核栈地址并不是如同 thread_union
描述的那样。
#04. 验证栈指针位置
验证方式与以上几次的方式相同,这里就仅展示具体代码和输出结果。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>static int __init print_stack_address_init(void)
{unsigned long sp = 0;unsigned long bp = 0;// 获取当前栈指针
#ifdef CONFIG_X86_64asm volatile("mov %%rsp, %0" : "=r" (sp)); // x86_64 使用 rspasm volatile("mov %%rbp, %0" : "=r" (bp)); // x86_64 使用 rbp
#elseasm volatile("mov %%esp, %0" : "=r" (sp)); // x86_32 使用 espasm volatile("mov %%ebp, %0" : "=r" (bp)); // x86_32 使用 ebp
#endifpr_info("task_struct->stack(stack base): %lx \n", (unsigned long **)(current->stack));pr_info("task_struct->stack(stack top): %lx \n", (unsigned long **)(current->stack) + THREAD_SIZE);pr_info("Current stack pointer (sp): %lx\n", sp);pr_info("Current frame pointer (bp): %lx\n", bp);return 0;
}static void __exit print_stack_address_exit(void)
{pr_info("Exiting module.\n");
}module_init(print_stack_address_init);
module_exit(print_stack_address_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Imagine Miracle");
MODULE_DESCRIPTION("Print kernel stack address.");
打印结果:
[74029.947307] task_struct->stack(stack base): ffffca9889e84000
[74029.947309] task_struct->stack(stack top): ffffca9889ea4000
[74029.947311] Current stack pointer (sp): ffffca9889e87928
[74029.947313] Current frame pointer (bp): ffffca9889e87940
#下一篇
《【Linux内核设计与实现】第三章——进程管理02》
相关文章:
【Linux内核设计与实现】第三章——进程管理01
文章目录 1. 引言2. 进程&线程——概念3. 进程控制块/进程描述符(PCB)4. 进程内核栈(Kernel Stack)4.1. 进程内核栈的定义4.2. thread_info 体系结构相关进程描述4.3. 定位进程描述符(task_struct)和内核栈以及内核栈指针的问题 5. 进程 IDÿ…...
正大模型视角下的市场结构判断逻辑
正大模型视角下的市场结构判断逻辑 在多数交易策略中,结构识别往往先于方向判断。以正大的数据研判风格为例,其核心逻辑是:价格行为不能孤立解读,必须结合时间与成交效率来判断当前结构的有效性。 例如,一个上涨过程&…...
4.25学习——文件上传之00截断
继昨天学习的基础文件上传内容,进一步学习文件上传的绕过方式 00截断绕过 原理:00截断是操作系统层的漏洞,由于操作系统是C语言或汇编语言编写的,这两种语言在定义字符串时,都是以\0(即0x00)作…...
黑马Redis(三)黑马点评项目
优惠卷秒杀 一、全局唯一ID 基于Redis实现全局唯一ID的策略: Component RequiredArgsConstructor public class RedisIdWorker {private static final Long BEGIN_TIMESTAMP1713916800L;private static final int COUNT_BITS 32;Resourceprivate final StringRed…...
dedecms织梦arclist标签noflag属性过滤多个参数
织梦dedecms系统arclist标签noflag属性默认是只能过滤一个参数,比如过滤推荐是noflagc,过滤有图片的文章是noflagc,在模板制作过程中,有时候我们为了seo和避免重复,需要过滤多个参数。今天小编就来跟大家讲讲织梦dedec…...
Jira、PingCode、Redmine等18款缺陷管理工具对比评测
本文主要介绍了以下:1. PingCode; 2. Worktile; 3. Jira; 4. Bugzilla; 5. TAPD; 6. 码云; 7. Redmine; 8. Trac; 9. 蓝鲸智云; 10. 阿里云效等等18款缺陷管理工具。 在现代软件开发和项目管理中,缺陷管理工具扮演着至关重要的角色。随着企业对软件质量的…...
京东以图搜图(拍立淘)API接口返回参数详解
京东以图搜图(拍立淘)API接口的返回参数通常以结构化JSON格式呈现,涵盖商品基础信息、相似度评分、库存状态及扩展字段,以下为关键参数详解及使用建议: 一、核心返回参数解析 状态标识类 status:请求状态…...
LSTM+KNN - 多元数据异常检测 !
大家好!我是我不是小 upper~ 今天想和大家分享一个超实用的案例:如何通过 LSTM 与 KNN 实现多元数据异常检测。 想象一下,在工厂的智能化监控场景中,各类传感器实时采集着温度、湿度、压力等海量数据。我们的目标,就是从中精准识别出设备潜在故障等异常情况。 LSTM 作为时…...
OpenHarmony之电源管理子系统公共事件定义
OpenHarmony之电源管理子系统公共事件定义 电源管理子系统面向应用发布如下系统公共事件,应用如需订阅系统公共事件,请参考公共事件接口文档。 COMMON_EVENT_BATTERY_CHANGED 表示电池充电状态、电平和其他信息发生变化的公共事件的动作。 值&#x…...
angular 实现可编辑可选择复制的表格
这个实现的核心就是ag-grid 当然有类似的库就不必多说,React, Vue和纯h5类似。简单贴一下代码 1.首先是h5部分,就一个id为supply-chain-material-grid-table的div,记住要设置高度 <div class"dki-supply-chain-page-body">…...
组织用户数统计实现
# 完整的组织用户数统计实现 完整的组织用户数统计实现,包括模拟SQL查询、完整的Java代码实现以及详细解释。 ## 1. 模拟SQL查询 假设我们有一个组织表(organization)和用户表(user),以下是模拟查询SQL: sql -- 获取各组织及其用户数量&a…...
天机学堂day10作业,完善兑换优惠券功能
UserCouponServiceImpl /*** 兑换码兑换优惠券* param code*/TransactionalOverridepublic void exchangeCoupon(String code) {//1、校验code是否为空if (StringUtils.isBlank(code)) {throw new BadRequestException("非法参数!");}//2、解析兑换码&…...
Python编程的真谛:超越语法,理解编程本质
你是否也曾陷入这样的误区:学了无数的 Python 语法、刷了几十套题,写起代码却仍然卡顿、举步维艰?这时候你才发现,真正阻碍进步的,从不是语法,而是你对“编程本质”的理解。 如果你只是死记硬背Python的语…...
C语言 函数补充
目录 static和extern函数 1.static和extern函数 static和extern都是C语言中的关键字 static 是 静态的 的意思,可以用来: - 修饰局部变量- 修饰全局变量- 修饰函数 extern 是用来声明外部符号的。 在讲解 static 和 extern 之前再讲一下: 作用域和生命周期。 …...
【AI图像创作变现】04实操路径—插图/绘本/创意图集
引言 如果说头像是“一个角色的起点”,那么插图、绘本和图集就是“这个角色能走多远”。相比于头像这种单图任务,插图类创作更强调批量性、叙事性与风格统一性,它既可以承载故事,也可以构成一套完整的内容产品结构。 这类任务特…...
Lesar: 面向 Lustre/Scade 语言的形式化模型检查工具
在《同步反应式系统》的第一课中,介绍了同步数据流语言 Lustre 生态中的形式化模型检查器 Lesar 的用法。Lesar 可对 lustre v4 语言以及 Scade 语言中部分数据流核心特性进行模型检查。 Lesar 介绍 Lesar 是 Verimag 研发维护的形式化方法模型检查工具。该工具的理…...
告别 “幻觉” 回答:RAG 中知识库与生成模型的 7 种对齐策略
一、引言 大语言模型(LLM)在文本生成领域展现出惊人能力,但 “幻觉” 问题(生成虚构或偏离事实的内容)始终是落地应用的核心挑战。检索增强生成(RAG)通过将外部知识库与 LLM 结合,形…...
【Web应用服务器_Tomcat】一、Tomcat基础与核心功能详解
在 Java Web 应用开发领域,Apache Tomcat 是一座不可或缺的基石。作为一款开源、轻量级的 Servlet 容器和 Web 服务器,Tomcat 以其稳定可靠、易于部署和高度可定制性,被广泛应用于各类 Web 应用的部署与运行。 一、Tomcat 简介 Tomcat 是…...
Cesium实现地形可视域分析
Cesium实现可视化分析 一、地形可视域主要实现技术(Ray + 地形碰撞检测) Cesium 本身的 Ray 类可以用来执行非常精确的射线检测,我们可以结合地形高度(sample)来逐点检测光线是否与 terrain 相交,从而判断是否可见。 1.1 优势 实时判断每条射线是否被 terrain 遮挡地形…...
Java—— 常见API介绍 第五期
JDK8以后新增的时间相关类 Date类ZoneId:时区Instant:时间戳ZoneDateTime:带时区的时间 日期格式化类 SimpleDateFormat DateTimeFormatter:用于时间的格式化和解析 日历类 Calendar LocalDate:年、月、日LocalTime…...
ViewPager FragmentPagerAdapter在系统杀死应用后重建时UI不刷新的问题
解决方案 通过重写getItemId方法,返回Fragment的hashCode: Override public long getItemId(int position) {/*** 恢复状态重建时,新的 Fragment 不刷新UI。* 原因:instantiateItem 中通过 mFragmentManager.findFragmentByTag(…...
第3讲、大模型如何理解和表示单词:词嵌入向量原理详解
1. 引言 大型语言模型(Large Language Models,简称LLM)如GPT-4、Claude和LLaMA等近年来取得了突破性进展,能够生成流畅自然的文本、回答复杂问题、甚至编写代码。但这些模型究竟是如何理解人类语言的?它们如何表示和处…...
关于STM32f1新建工程
创建文件夹 首先创建一个存放工程的文件夹,建议建立在D,E盘 新建工程 在kiel5里面 找到刚刚建立的文件夹,然后在此文件夹里面新建一个文件夹用来存放本次工程,文件夹可以根据工程内容所编写,然后给自己工程也就是…...
Linux:进程间通信---匿名管道
文章目录 1. 进程间通信1.1 什么是进程间通信?1.2 为什么进程要进行进程间通信?1.3 怎么实现进程间通信? 2. 匿名管道2.1 匿名管道的原理2.2 匿名管道的系统接口2.3 匿名管道的使用2.4 匿名管道的运用场景 序:在上一篇文章中我们知…...
python代做推荐系统深度学习知识图谱c#代码代编神经网络算法创新
以下是针对推荐系统、深度学习、知识图谱和神经网络算法创新的代码框架及开发建议,适用于C#和Python的跨语言协作项目。以下内容分为几个部分,涵盖技术选型、代码示例和创新方向。 1. 推荐系统(Python C#) Python部分࿰…...
【动手学大模型开发】VSCode 连接远程服务器
Visual Studio Code(VSCode)是一款由微软开发的免费、开源的现代化代码编辑器。它以其轻量级、高性能和广泛的编程语言支持而受到开发者的青睐。VSCode 的核心特点包括: 跨平台:支持 Windows、macOS 和 Linux 操作系统。扩展市场…...
PostgreSQL 漏洞信息详解
PostgreSQL 漏洞信息详解 PostgreSQL 作为一款开源关系型数据库,其安全漏洞会被社区及时发现和修复。以下是 PostgreSQL 漏洞相关的重要信息和资源。 一、主要漏洞信息来源 1. 官方安全公告 PostgreSQL 安全信息页面:https://www.postgresql.org/sup…...
华为L410上制作内网镜像模板:在客户端配置模板内容
华为L410上制作内网镜像模板:在客户端配置模板内容 在本教程中,我们将继续在华为L410上配置内网镜像模板,具体介绍如何在客户端设置以便于在首次开机时自动安装软件。我们将主要使用WeChat作为示例。 1. 制作镜像模板,开启 rc.l…...
分布式队列对消息语义的处理
在分布式系统中,消息的处理语义(Message Processing Semantics)是确保系统可靠性和一致性的关键。有三种语义: 在分布式系统中,消息的处理语义(Message Processing Semantics)是确保系统可靠性和…...
《免费开放”双刃剑:字节跳动Coze如何撬动AI生态霸权与暗涌危机?》
战略动机分析 降低技术门槛为数据采集接口 Coze平台宣称**“30秒无代码生成AI Bot”,大幅降低了企业开发AI应用的技术门槛。任何不懂编程的业务人员都可以通过可视化流程和提示词,在半分钟内搭建聊天机器人或智能代理。这种极低门槛意味着更多企业和个人…...
AI 开发工具提示词集体开源!解锁 Cursor、Cline、Windsurf 等工具的核心逻辑
✨ 前言:提示词,AI 编程工具的灵魂 随着大模型编程能力的迅速提升,AI 编程工具如雨后春笋般涌现,涵盖了从代码编辑器(如 Cursor、Windsurf、Cline)到应用生成服务(如 Lovable、Bolt.new、V0&am…...
MYSQL 常用字符串函数 和 时间函数详解
一、字符串函数 1、CONCAT(str1, str2, …) 拼接多个字符串。 SELECT CONCAT(Hello, , World); -- 输出 Hello World2、SUBSTRING(str, start, length) 或 SUBSTR() 截取字符串。 SELECT SUBSTRING(MySQL, 3, 2); -- 输出 SQ3、LENGTH(str) 与 CHAR_LENGTH…...
Ubuntu 下 Nginx 1.28.0 源码编译安装与 systemd 管理全流程指南
一、环境与依赖准备 为确保编译顺利,我们首先更新系统并安装必要的编译工具和库: sudo apt update sudo apt install -y build-essential \libpcre3 libpcre3-dev \zlib1g zlib1g-dev \libssl-dev \wgetbuild-essential:提供 gcc、make 等基…...
线程怎么创建?Java 四种方式一网打尽
🚀 Java 中线程的 4 种创建方式详解 创建方式实现方式是否推荐场景说明1. 继承 Thread 类class MyThread extends Thread❌ 不推荐简单学习、单线程场景2. 实现 Runnable 接口class MyRunnable implements Runnable✅ 推荐更适合多线程共享资源3. 实现 Callable 接…...
高效使用DeepSeek对“情境+ 对象 +问题“型课题进行开题!
目录 思路"情境 对象 问题"型 课题选题的类型有哪些呢?这要从课题题目的构成说起。通过对历年来国家社会科学基金立项项目进行分析,小编发现,课题选题类型非常丰富,但一般是围绕限定词、研究对象和研究问题进行不同的组…...
【GCC bug】libstdc++.so.6: version `GLIBCXX_3.4.29‘ not found
在 conda 环境安装 gcc/gxx 之后,运行开始遇到了以下的报错 File "/mnt/data/home/xxxx/miniforge3/envs/GAGAvatar/lib/python3.12/site-packages/google/protobuf/internal/wire_format.py", line 13, in <module>from google.protobuf import de…...
python卸载报错:No Python 3.12 installation was detected已解决
问题背景 在卸载Python 3.12.5时,遇到了一个棘手的问题:运行安装包python.exe点击Uninstall后,系统提示No Python 3.12 installation was detected. 尝试了网上各种方法(包括注册表清理、修复repair,卸载unins…...
【Hive入门】Hive分区与分区表完全指南:从原理到企业级实践
引言 在大数据时代,高效管理海量数据成为企业面临的核心挑战。Hive作为Hadoop生态系统中最受欢迎的数据仓库解决方案,其分区技术是优化数据查询和管理的关键手段。本文将全面解析Hive分区技术的原理、实现方式及企业级最佳实践,帮助您构建高性…...
AI之FastAPI+ollama调用嵌入模型OllamaBgeEmbeddings
以下是对该 FastAPI 代码的逐行解析和详细说明: 代码结构概览 from fastapi import Depends # 导入依赖注入模块def get_embedder():return OllamaBgeEmbeddings(base_url="http://ollama-cluster:11434",timeout=30,max_retries=5)@app.post("/embed")…...
RK3588芯片NPU的使用:yolov8-pose例子图片检测在安卓系统部署与源码深度解析(rknn api)
一、本文的目标 将yolo8-pose例子适配安卓端,提供选择图片后进行姿态识别功能。通过项目学习源码和rknn api。二、开发环境说明 主机系统:Windows 11目标设备:搭载RK3588芯片的安卓开发板核心工具:Android Studio Koala | 2024.1.1 Patch 2,NDK 27.0三、适配(迁移)安卓 …...
【HTTP/3:互联网通信的量子飞跃】
HTTP/3:互联网通信的量子飞跃 如果说HTTP/1.1是乡村公路,HTTP/2是现代高速公路系统,那么HTTP/3就像是一种革命性的"传送门"技术,它彻底重写了数据传输的底层规则,让信息几乎可以瞬间抵达目的地,…...
2024 年:Kubernetes 包管理的新前沿
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:历代文学,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程,高并发设计…...
SIEMENS PLC 程序 GRAPH 程序解读 车型入库
1、程序载图1 2、程序截图2 3、程序解释 这是一个基于西门子 GRAPH 编程的车型 1 入库顺序控制流程图,通过状态机结构(状态框 S 与转移条件 T)描述完整工作流程,具体如下: 整体流程概述 初始化:从 S1&am…...
c++11新特性随笔
1.统一初始化特性 c98中不支持花括号进行初始化,编译时会报错,在11当中初始化可以通过{}括号进行统一初始化。 c98编译报错 c11: #include <iostream> #include <set> #include <string> #include <vector>int main() {std:…...
微信小程序文章管理系统开发实现
概述 在内容为王的互联网时代,高效的文章管理系统成为各类平台的刚需。幽络源平台今日分享一款基于SSM框架开发的微信小程序文章管理系统完整解决方案,该系统实现了多角色内容管理、智能分类、互动交流等功能。 主要内容 一、用户端功能模块 多角…...
3种FSC标签你用对了吗?
如果你留意过产品上的FSC小树标识,也许会发现它们很相似但又各不相同。 根据产品使用的FSC认证材料的不同比例,共有三种不同类型的FSC标签: 1、FSC 100% 所有使用的材料均来自负责任管理的FSC认证森林。 标签文本为:“ From well-…...
NLP高频面试题(五十四)——深度学习归一化详解
引言:大模型训练中的归一化需求 随着人工智能技术的快速发展,**大模型(Large Language Models, LLMs)**的规模与能力都呈爆发式增长。诸如GPT-4、BERT、PaLM等模型参数量从最初的百万级到如今的千亿、万亿级别,训练难度和效率问题日益显著。在超大模型的训练过程中,梯度…...
第5.5章:ModelScope-Agent:支持多种API无缝集成的开源框架
5.5.1 ModelScope-Agent概述 ModelScope-Agent,由阿里巴巴旗下ModelScope社区开发,是一个开源的、模块化的框架,旨在帮助开发者基于大型语言模型快速构建功能强大、灵活性高的智能代理。它的核心优势在于支持与多种API和外部系统的无缝集成&…...
筑牢数字防线:商城系统安全的多维守护策略
一、构建网络安全防护屏障 网络安全是商城系统安全的第一道防线。企业应采用先进的防火墙技术,实时监控和过滤进出网络的流量,阻止非法访问和恶意攻击。入侵检测与防御系统(IDS/IPS)也是不可或缺的安全组件,它能够及…...
PTC加热片详解(STM32)
目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 三、程序设计 main文件 jdq.h文件 jdq.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 PTC是正温度系数热敏电阻的英文简称,其电阻值随着PTC热敏电阻本体温度的升高呈现阶跃性的增加。温度越高&…...