Linux系统——线程概念与控制之杂谈

发布时间:2026/11/6 3:38:54
Linux系统——线程概念与控制之杂谈 ‍♂️个人主页进击的荆棘作者其它专栏数据结构与算法《数据结构与算法》算法《算法》C《C》Linux《Linux系统》MySQL《MySQL》目录1.Linux线程概念2.Linux进程vs线程3.Linux线程控制4.线程ID及进程地址空间布局5.线程封装6.附录6.附录6.1源码阅读理解线程下create_threadstatic intcreate_thread(struct pthread *pd, const struct pthread_attr *attr,STACK_VARIABLES_PARMS){#ifdef TLS_TCB_AT_TPassert(pd-header.tcb ! NULL);#endif/* We rely heavily on various flags the CLONE function understands:CLONE_VM, CLONE_FS, CLONE_FILESThese flags select semantics with shared address space andfile descriptors according to what POSIX requires.CLONE_SIGNALThis flag selects the POSIX signal semantics.CLONE_SETTLSThe sixth parameter to CLONE determines the TLS area for thenew thread.CLONE_PARENT_SETTIDThe kernels writes the thread ID of the newly created threadinto the location pointed to by the fifth parameters to CLONE.Note that it would be semantically equivalent to useCLONE_CHILD_SETTID but it is be more expensive in the kernel.CLONE_CHILD_CLEARTIDThe kernels clears the thread ID of a thread that has calledsys_exit() in the location pointed to by the seventh parameterto CLONE.CLONE_DETACHEDNo signal is generated if the thread exists and it isautomatically reaped.The termination signal is chosen to be zero which means no signalis sent. */int clone_flags (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL |CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM#if __ASSUME_NO_CLONE_DETACHED 0| CLONE_DETACHED#endif| 0);if (__builtin_expect(THREAD_GETMEM(THREAD_SELF, report_events), 0)){/* The parent thread is supposed to report events. Check whetherthe TD_CREATE event is needed, too. */const int _idx __td_eventword(TD_CREATE);const uint32_t _mask __td_eventmask(TD_CREATE);if ((_mask (__nptl_threads_events.event_bits[_idx] | pd - eventbuf.eventmask.event_bits[_idx])) ! 0){/* We always must have the thread start stopped. */pd-stopped_start true;/* Create the thread. We always create the thread stoppedso that it does not get far before we tell the debugger. */int res do_clone(pd, attr, clone_flags, start_thread,STACK_VARIABLES_ARGS, 1);if (res 0){/* Now fill in the information about the new thread inthe newly created threads data structure. We cannot letthe new thread do this since we dont know whether it wasalready scheduled when we send the event. */pd-eventbuf.eventnum TD_CREATE;pd-eventbuf.eventdata pd;/* Enqueue the descriptor. */dopd-nextevent __nptl_last_event;while (atomic_compare_and_exchange_bool_acq(__nptl_last_event,pd, pd-nextevent) ! 0);/* Now call the function which signals the event. */__nptl_create_event();/* And finally restart the new thread. */lll_unlock(pd-lock);}return res;}}#ifdef NEED_DL_SYSINFOassert(THREAD_SELF_SYSINFO THREAD_SYSINFO(pd));#endif/* Determine whether the newly created threads has to be startedstopped since we have to set the scheduling parameters or set theaffinity. */bool stopped false;if (attr ! NULL (attr-cpuset ! NULL || (attr-flags ATTR_FLAG_NOTINHERITSCHED) ! 0))stopped true;pd-stopped_start stopped;/* Actually create the thread. */int res do_clone(pd, attr, clone_flags, start_thread,STACK_VARIABLES_ARGS, stopped);if (res 0 stopped)/* And finally restart the new thread. */lll_unlock(pd-lock);return res;}do_clonestatic intdo_clone(struct pthread *pd, const struct pthread_attr *attr,int clone_flags, int (*fct)(void *), STACK_VARIABLES_PARMS,int stopped){#ifdef PREPARE_CREATEPREPARE_CREATE;#endifif (stopped)/* We Make sure the thread does not run far by forcing it to get alock. We lock it here too so that the new thread cannot continueuntil we tell it to. */lll_lock(pd-lock);/* One more thread. We cannot have the thread do this itself, since itmight exist but not have been scheduled yet by the time weve returnedand need to check the value to behave correctly. We must do it beforecreating the thread, in case it does get scheduled first and thenmight mistakenly think it was the only thread. In the failure case,we momentarily store a false value; this doesnt matter because thereis no kosher thing a signal handler interrupting us right here can dothat cares whether the thread count is correct. */atomic_increment(__nptl_nthreads);// 执⾏特性体系结构下的clone函数if (ARCH_CLONE(fct, STACK_VARIABLES_ARGS, clone_flags,pd, pd-tid, TLS_VALUE, pd-tid) -1){atomic_decrement(__nptl_nthreads); /* Oops, we lied for a second. *//* Failed. If the thread is detached, remove the TCB here sincethe caller cannot do this. The caller remembered the threadas detached and cannot reverify that it is not since it mustnot access the thread descriptor again. */if (IS_DETACHED(pd))__deallocate_stack(pd);return errno;}/* Now we have the possibility to set scheduling parameters etc. */// 下⾯是调⽤相关系统调⽤设置轻量级进程的调度参数和⼀些异常处理不关⼼if (__builtin_expect(stopped ! 0, 0)){INTERNAL_SYSCALL_DECL(err);int res 0;/* Set the affinity mask if necessary. */if (attr-cpuset ! NULL){res INTERNAL_SYSCALL(sched_setaffinity, err, 3, pd-tid,sizeof(cpu_set_t), attr-cpuset);if (__builtin_expect(INTERNAL_SYSCALL_ERROR_P(res, err), 0)){/* The operation failed. We have to kill the thread. Firstsend it the cancellation signal. */INTERNAL_SYSCALL_DECL(err2);err_out:#if __ASSUME_TGKILL(void)INTERNAL_SYSCALL(tgkill, err2, 3,THREAD_GETMEM(THREAD_SELF, pid),pd-tid, SIGCANCEL);#else(void)INTERNAL_SYSCALL(tkill, err2, 2, pd-tid, SIGCANCEL);#endifreturn (INTERNAL_SYSCALL_ERROR_P(res, err)? INTERNAL_SYSCALL_ERRNO(res, err): 0);}}/* Set the scheduling parameters. */if ((attr-flags ATTR_FLAG_NOTINHERITSCHED) ! 0){res INTERNAL_SYSCALL(sched_setscheduler, err, 3, pd-tid,pd-schedpolicy, pd-schedparam);if (__builtin_expect(INTERNAL_SYSCALL_ERROR_P(res, err), 0))goto err_out;}}/* We now have for sure more than one thread. The main thread mightnot yet have the flag set. No need to set the global variableagain if this is what we use. */THREAD_SETMEM(THREAD_SELF, header.multiple_threads, 1);return 0;}#define ARCH_CLONE __clone__clone是glibc⽤汇编封装的⼀个调⽤clone系统调⽤的函数所以__clone的实现就是汇编贴⼀份代码sysdeps/unix/sysv/linux/x86_64ENTRY (BP_SYM (__clone))/* Sanity check arguments. */movq $-EINVAL,%raxtestq %rdi,%rdi /* no NULL function pointers */jz SYSCALL_ERROR_LABELtestq %rsi,%rsi /* no NULL stack pointers */jz SYSCALL_ERROR_LABEL/* Insert the argument onto the new stack. */subq $16,%rsimovq %rcx,8(%rsi)/* Save the function pointer. It will be popped off in thechild in the ebx frobbing below. */movq %rdi,0(%rsi)/* Do the system call. */movq %rdx, %rdimovq %r8, %rdxmovq %r9, %r8movq 8(%rsp), %r10movl $SYS_ify(clone),%eax // 获取系统调⽤号/* End FDE now, because in the child the unwind info will bewrong. */cfi_endproc;syscall // 陷⼊内核(x86_32是int 80)要求内核创建轻量级进程testq %rax,%raxjl SYSCALL_ERROR_LABELjz L(thread_start)这部分代码了解即可。空间申请int errALLOCATE_STACK(iattr,pd);源码路径nptl / allocatestack.c// 空间申请的函数其实就是⼀个宏#define ALLOCATE_STACK(attr, pd) \allocate_stack(attr, pd, stackaddr, stacksize)static intallocate_stack(const struct pthread_attr *attr,struct pthread **pdp,ALLOCATE_STACK_PARMS){struct pthread *pd;size_t size;size_t pagesize_m1 __getpagesize() - 1;void *stacktop;assert(attr ! NULL);assert(powerof2(pagesize_m1 1));assert(TCB_ALIGNMENT STACK_ALIGN);/* Get the stack size from the attribute if it is set. Otherwise weuse the default we determined at start time. */size attr-stacksize ?: __default_stacksize; // 获取栈⼤⼩⽤⼾没设置就默认/* Get memory for the stack. */// 如果已经⽤⼾已经在线程属性⾥⾯设置了空间就直接⽤我们是默认这部分代码直接不看if (__builtin_expect(attr-flags ATTR_FLAG_STACKADDR, 0)){uintptr_t adj;/* If the user also specified the size of the stack make sure itis large enough. */if (attr-stacksize ! 0 attr-stacksize (__static_tls_size MINIMAL_REST_STACK))return EINVAL;/* Adjust stack size for alignment of the TLS block. */#if TLS_TCB_AT_TPadj ((uintptr_t)attr-stackaddr - TLS_TCB_SIZE) __static_tls_align_m1;assert(size adj TLS_TCB_SIZE);#elif TLS_DTV_AT_TPadj ((uintptr_t)attr-stackaddr - __static_tls_size) __static_tls_align_m1;assert(size adj);#endif/* The user provided some memory. Lets hope it matches thesize... We do not allocate guard pages if the user providedthe stack. It is the users responsibility to do this if itis wanted. */#if TLS_TCB_AT_TPpd (struct pthread *)((uintptr_t)attr-stackaddr - TLS_TCB_SIZE - adj);#elif TLS_DTV_AT_TPpd (struct pthread *)(((uintptr_t)attr-stackaddr - __static_tls_size -adj) -TLS_PRE_TCB_SIZE);#endif/* The user provided stack memory needs to be cleared. */memset(pd, \0, sizeof(struct pthread));/* The first TSD block is included in the TCB. */pd-specific[0] pd-specific_1stblock;/* Remember the stack-related values. */pd-stackblock (char *)attr-stackaddr - size;pd-stackblock_size size;/* This is a user-provided stack. It will not be queued in thestack cache nor will the memory (except the TLS memory) be freed. */pd-user_stack true;/* This is at least the second thread. */pd-header.multiple_threads 1;#ifndef TLS_MULTIPLE_THREADS_IN_TCB__pthread_multiple_threads *__libc_multiple_threads_ptr 1;#endif#ifdef NEED_DL_SYSINFO/* Copy the sysinfo value from the parent. */THREAD_SYSINFO(pd) THREAD_SELF_SYSINFO;#endif/* The process ID is also the same as that of the caller. */pd-pid THREAD_GETMEM(THREAD_SELF, pid);/* List of robust mutexes. */#ifdef __PTHREAD_MUTEX_HAVE_PREVpd-robust_list.__prev pd-robust_list;#endifpd-robust_list.__next pd-robust_list;/* Allocate the DTV for this thread. */if (_dl_allocate_tls(TLS_TPADJ(pd)) NULL){/* Something went wrong. */assert(errno ENOMEM);return EAGAIN;}/* Prepare to modify global data. */lll_lock(stack_cache_lock);/* And add to the list of stacks in use. */list_add(pd-list, __stack_user);lll_unlock(stack_cache_lock);}else{// 下⾯的都是库内部⾃⼰做的我们关⼼的/* Allocate some anonymous memory. If possible use the cache. */size_t guardsize;size_t reqsize;void *mem;const int prot (PROT_READ | PROT_WRITE | ((GL(dl_stack_flags) PF_X) ? PROT_EXEC : 0));#if COLORING_INCREMENT ! 0/* Add one more page for stack coloring. Dont do it for stackswith 16 times pagesize or larger. This might just causeunnecessary misalignment. */if (size 16 * pagesize_m1)size pagesize_m1 1;#endif/* Adjust the stack size for alignment. */size ~__static_tls_align_m1; // 设置空间对⻬assert(size ! 0);/* Make sure the size of the stack is enough for the guard andeventually the thread descriptor. */guardsize (attr-guardsize pagesize_m1) ~pagesize_m1;if (__builtin_expect(size ((guardsize __static_tls_size MINIMAL_REST_STACK pagesize_m1) ~pagesize_m1),0))/* The stack is too small (or the guard too large). */return EINVAL;/* Try to get a stack from the cache. */// 先尝试从pthread缓存中申请空间reqsize size;pd get_cached_stack(size, mem);if (pd NULL){/* To avoid aliasing effects on a larger scale than pages weadjust the allocated stack size if necessary. This wayallocations directly following each other will not havealiasing problems. */#if MULTI_PAGE_ALIASING ! 0if ((size % MULTI_PAGE_ALIASING) 0)size pagesize_m1 1;#endif// 缓存申请失败就在堆空间申请私有的匿名内存空间这⾥mmap类似malloc// 当然他也可以作为共享内存的实现类似原理我们接触过这个功能和当前⽆关mem mmap(NULL, size, prot,MAP_PRIVATE | MAP_ANONYMOUS | ARCH_MAP_FLAGS, -1, 0);if (__builtin_expect(mem MAP_FAILED, 0)){#ifdef ARCH_RETRY_MMAPmem ARCH_RETRY_MMAP(size);if (__builtin_expect(mem MAP_FAILED, 0))#endifreturn errno;}/* SIZE is guaranteed to be greater than zero.So we can never get a null pointer back from mmap. */assert(mem ! NULL);#if COLORING_INCREMENT ! 0/* Atomically increment NCREATED. */unsigned int ncreated atomic_increment_val(nptl_ncreated);/* We chose the offset for coloring by incrementing it forevery new thread by a fixed amount. The offset usedmodule the page size. Even if coloring would be betterrelative to higher alignment values it makes no sense todo it since the mmap() interface does not allow us tospecify any alignment for the returned memory block. */size_t coloring (ncreated * COLORING_INCREMENT) pagesize_m1;/* Make sure the coloring offsets does not disturb the alignmentof the TCB and static TLS block. */if (__builtin_expect((coloring __static_tls_align_m1) ! 0, 0))coloring (((coloring __static_tls_align_m1) ~(__static_tls_align_m1)) ~pagesize_m1);#else/* Unless specified we do not make any adjustments. */#define coloring 0#endif/* Place the thread descriptor at the end of the stack. */// 下⾯的代码其实就是我们的图这⾥是在申请的空间中确定structthread(tcb) 的地址#if TLS_TCB_AT_TPpd (struct pthread *)((char *)mem size - coloring) - 1;#elif TLS_DTV_AT_TPpd (struct pthread *)((((uintptr_t)mem size - coloring -__static_tls_size) ~__static_tls_align_m1) -TLS_PRE_TCB_SIZE);#endif/* Remember the stack-related values. */// 记录下来整个空间的地址和⼤⼩pd-stackblock mem;pd-stackblock_size size;/* We allocated the first block thread-specific data array.This address will not change for the lifetime of thisdescriptor. */pd-specific[0] pd-specific_1stblock;/* This is at least the second thread. */pd-header.multiple_threads 1;#ifndef TLS_MULTIPLE_THREADS_IN_TCB__pthread_multiple_threads *__libc_multiple_threads_ptr 1;#endif#ifdef NEED_DL_SYSINFO/* Copy the sysinfo value from the parent. */THREAD_SYSINFO(pd) THREAD_SELF_SYSINFO;#endif/* The process ID is also the same as that of the caller. */// 获取线程对应进程的pidpd-pid THREAD_GETMEM(THREAD_SELF, pid);/* List of robust mutexes. */#ifdef __PTHREAD_MUTEX_HAVE_PREVpd-robust_list.__prev pd-robust_list;#endifpd-robust_list.__next pd-robust_list;/* Allocate the DTV for this thread. */if (_dl_allocate_tls(TLS_TPADJ(pd)) NULL){/* Something went wrong. */assert(errno ENOMEM);/* Free the stack memory we just allocated. */(void)munmap(mem, size);return EAGAIN;}/* Prepare to modify global data. */lll_lock(stack_cache_lock);/* And add to the list of stacks in use. */list_add(pd-list, stack_used);lll_unlock(stack_cache_lock);/* There might have been a race. Another thread might havecaused the stacks to get exec permission while this newstack was prepared. Detect if this was possible andchange the permission if necessary. */if (__builtin_expect((GL(dl_stack_flags) PF_X) ! 0 (prot PROT_EXEC) 0,0)){int err change_stack_perm(pd#ifdef NEED_SEPARATE_REGISTER_STACK,~pagesize_m1#endif);if (err ! 0){/* Free the stack memory we just allocated. */(void)munmap(mem, size);return err;}}/* Note that all of the stack and the thread descriptor iszeroed. This means we do not have to initialize fieldswith initial value zero. This is specifically true forthe tid field which is always set back to zero once thestack is not used anymore and for the guardsize fieldwhich will be read next. */}/* Create or resize the guard area if necessary. */if (__builtin_expect(guardsize pd-guardsize, 0)){#ifdef NEED_SEPARATE_REGISTER_STACKchar *guard mem (((size - guardsize) / 2) ~pagesize_m1);#elsechar *guard mem;#endifif (mprotect(guard, guardsize, PROT_NONE) ! 0){int err;mprot_error:err errno;lll_lock(stack_cache_lock);/* Remove the thread from the list. */list_del(pd-list);lll_unlock(stack_cache_lock);/* Get rid of the TLS block we allocated. */_dl_deallocate_tls(TLS_TPADJ(pd), false);/* Free the stack memory regardless of whether the sizeof the cache is over the limit or not. If this pieceof memory caused problems we better do not use itanymore. Uh, and we ignore possible errors. Thereis nothing we could do. */(void)munmap(mem, size);return err;}pd-guardsize guardsize;}else if (__builtin_expect(pd-guardsize - guardsize size - reqsize,0)){/* The old guard area is too large. */#ifdef NEED_SEPARATE_REGISTER_STACKchar *guard mem (((size - guardsize) / 2) ~pagesize_m1);char *oldguard mem (((size - pd-guardsize) / 2) ~pagesize_m1);if (oldguard guard mprotect(oldguard, guard - oldguard, prot) ! 0)goto mprot_error;if (mprotect(guard guardsize,oldguard pd-guardsize - guard - guardsize,prot) ! 0)goto mprot_error;#elseif (mprotect((char *)mem guardsize, pd-guardsize - guardsize,prot) ! 0)goto mprot_error;#endifpd-guardsize guardsize;}/* The pthread_getattr_np() calls need to get passed the sizerequested in the attribute, regardless of how large theactually used guardsize is. */pd-reported_guardsize guardsize;}/* Initialize the lock. We have to do this unconditionally since thestillborn thread could be canceled while the lock is taken. */pd-lock LLL_LOCK_INITIALIZER;/* We place the thread descriptor at the end of the stack. */// ⼆级指针返回struct thread的地址其实就是⼀个堆块的地址对⽐之前的⽰意图*pdp pd;#if TLS_TCB_AT_T/* The stack begins before the TCB and the static TLS block. */stacktop ((char *)(pd 1) - __static_tls_size);#elif TLS_DTV_AT_TPstacktop (char *)(pd - 1);#endif#ifdef NEED_SEPARATE_REGISTER_STACK*stack pd-stackblock;*stacksize stacktop - *stack;#else*stack stacktop;#endifreturn 0;}// 所以在创建线程的时候其实就是在pthread库内部创建好描述线程的结构体对象填充属性// 第⼆步就是调⽤clone让内核创建轻量级进程并执⾏传⼊的回调函数和参数// 其实库提供的⽆⾮就是未来操作线程的API通过属性设置线程的优先级之类⽽真正调度的// 过程还是内核来的。// 但是如果我们⾃⼰在上层设计⼀些让线程暂停出让CPU然后我们上次⾃定义队列让线程的tcb进⾏排队// 那么我们其实也可以基于内核在⽤⼾层实现线程的调度很多更⾼级的语⾔可能会做这个⼯作。/* How to pass the values to the create_thread function. */#define STACK_VARIABLES_ARGS stackaddr // STACK_VARIABLES_ARGS其实就是stack地址6.2线程栈虽然Linux将线程和进程不加区别的统一到了task_struct ,但是对待其地址空间stack还是有区别的●对于Linux进程或者说主线程简单理解就是main函数的栈空间在fork的时候实际上就是复制了父亲的stack空间地址然后写时拷贝cow以及动态增长。若扩充超出该上限则栈溢出会报段错误发送段错误信号给该进程。进程栈是唯一可以访问为映射页而不一定发送段错误——超出补充上限才报●然而对于主线程生成的子线程而言其stack将不再是向下生长的而是事先固定下来的。线程栈一般是调用gblic/uclibc等的pthread库接口pthread_create创建的线程在文件映射区或称之为共享区。其中使用mmap系统调用这个可以从glibc的nptl/allocatstack.c中的allocate_stack函数中看到此调用中的size参数的获取很复杂可以手动传入stack的大小也可以是使用默认的一般而言就是默认的8M。这些都不重要重要的是这种stack不能动态增长一旦用尽就没了这是和生成进程的fork不同的地方。在glibc中通过mmap得到stack后底层将调用sys_clone系统调用int sys_clone(struct pt_regs *regs) { unsigned long clone_flags; unsigned long newsp; int __user *parent_tidptr, *child_tidptr; clone_flags regs-bx; // 获取了mmap得到的线程的stack指针 newsp regs-cx; parent_tidptr (int __user *)regs-dx; child_tidptr (int __user *)regs-di; if (!newsp) newsp regs-sp; return do_fork(clone_flags, newsp, regs, 0, parent_tidptr, child_tidptr); }因此对于子线程的stack它其实是在进程的地址空间中map出来的一块内存空间原则上是线程私有的但是同一个进程的所有线程生成的时候是会浅拷贝生成者的task_struct的很多字段若想其它线程也还是可以访问的。6.3页表和页表项* We keep two sets of PTEs - the hardware and the linux version. * This allows greater flexibility in the way we map the Linux bits * onto the hardware tables, and allows us to have YOUNG and DIRTY * bits. * * The PTE table pointer refers to the hardware entries; the Linux * entries are stored 1024 bytes below. */ // ⻚表标志位 #define L_PTE_PRESENT (1 0) #define L_PTE_FILE (1 1) /* only when !PRESENT */ #define L_PTE_YOUNG (1 1) #define L_PTE_BUFFERABLE (1 2) /* matches PTE */ #define L_PTE_CACHEABLE (1 3) /* matches PTE */ #define L_PTE_USER (1 4) #define L_PTE_WRITE (1 5) #define L_PTE_EXEC (1 6) #define L_PTE_DIRTY (1 7) #define L_PTE_COHERENT (1 9) /* I/O coherent (xsc3) */ #define L_PTE_SHARED (1 10) /* shared between CPUs (v6) */ #define L_PTE_ASID (1 11) /* non-global (use ASID, v6) */ // ⻚表是 typedef struct { unsigned long pte; } pte_t; // ⻚表项 typedef struct { unsigned long pgd; } pgd_t; // ⻚全局⽬录项pgd_t *pgd_alloc(struct mm_struct *mm){pgd_t *ret, *init;ret (pgd_t *)__get_free_page(GFP_KERNEL | __GFP_ZERO);init pgd_offset(init_mm, 0UL);if (ret){#ifdef CONFIG_ALPHA_LARGE_VMALLOCmemcpy(ret USER_PTRS_PER_PGD, init USER_PTRS_PER_PGD,(PTRS_PER_PGD - USER_PTRS_PER_PGD - 1) * sizeof(pgd_t));#elsepgd_val(ret[PTRS_PER_PGD - 2]) pgd_val(init[PTRS_PER_PGD - 2]);#endif/* The last PGD entry is the VPTB self-map. */pgd_val(ret[PTRS_PER_PGD - 1]) pte_val(mk_pte(virt_to_page(ret), PAGE_KERNEL));}return ret;}pte_t *pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address){pte_t *pte (pte_t *)__get_free_page(GFP_KERNEL | __GFP_REPEAT | __GFP_ZERO);return pte;}struct mm_struct{struct vm_area_struct *mmap; /* list of VMAs */struct rb_root mm_rb;struct vm_area_struct *mmap_cache; /* last find_vma result */unsigned long (*get_unmapped_area)(struct file *filp,unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags);void (*unmap_area)(struct mm_struct *mm, unsigned long addr);unsigned long mmap_base; /* base of mmap area */unsigned long task_size; /* size of task vm space */unsigned long cached_hole_size; /* if non-zero, the largest holebelow free_area_cache */unsigned long free_area_cache; /* first hole of sizecached_hole_size or larger */pgd_t *pgd;// ⻚⽬录起始地址}

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询