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

发布时间:2026/11/6 3:39:08

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;// ⻚⽬录起始地址}
延伸阅读

更多相关文章

2026/11/6 3:39:08

技术人往上走,要读懂财务岗位背后的价值分层

1、为什么技术人需要懂财务 技术人的成长路径通常是这样的: 写代码 → 带项目 → 管团队 → 负责一条业务线 → 成为技术VP/CTO→创业 越往上走,你面对的问题就越不再是"这个系统怎么搭",而是: 这个项目该不该投?ROI多少? 团队扩招20人,对利润的影响是什么…

2026/11/6 3:39:08

一个人只能盯 4 个会话:Super-agent 的 Router-Worker 拆法

一个人只能盯 4 个会话:Super-agent 的 Router-Worker 拆法原文:DeepSeek Harness - 《Super-agent 设计探索:迈向更自主的 Agent 系统》(https://www.ithome.com/1/020/615.htm)现在主流的 Agent 设计都以单个 Sessio…

2026/11/6 4:54:22

使用LangChain实现RAG流程:从数据读取到效果评估的全流程解析

引言 随着自然语言处理(NLP)和大模型技术的发展,构建基于大模型的知识库检索增强生成(RAG, Retrieval-Augmented Generation)系统成为许多项目的重要组成部分。本文将详细介绍我如何结合LangChain实现一个完整的RAG流程…

2026/11/6 4:54:22

慈様や 前端学习导航‍

慈様や 前端学习导航👩🏻‍🚀🚀 ✍忙里偷闲,来写一下Blog导航—— 一直都想做这个,但因为前端的文章还没写多少,不够撑起来…就一直没写, 加上前两天天天加班、也没有&#x1f55…

2026/11/6 4:54:22

uni-app 定位 SDK:维智全方位赋能开发者的位置服务

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 4:49:21

MyBatis(随笔2 : Sql映射文件 )

Sql文件映射 这里只是笔记,属性详情,关键还是要多练 ——实例代码 MyBatisDemo MyBatis 真正的强大在于映射语句,专注于SQL,功能强大,SQL映射的配置却是相当简单 SQL映射文件的几个顶级元素 name详情mappernamespace: 命名空间cache配置给…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 1:03:39

MCU晶振电路设计全解析:从负载电容计算到PCB布局调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑