Linux 进程的一生(一):进程与线程的创建机制解析
在 Linux 操作系统中,每个任务都以「进程」的形式存在。但 Linux 下的「线程」又是什么?Linux 并没有单独定义一种全新数据结构来表示线程,而是将线程视为一种特殊的进程——一种共享资源的轻量级进程。然而,在具体实现和运行机制上,线程与进程又存在诸多关键差异,这也使得理解二者的区别和联系非常重要。
进程和线程共享许多核心机制,但在创建、调度、资源共享、同步和消亡等环节又体现出微妙的不同。例如:
- 进程通常通过
fork()系统调用创建,而线程更多地使用pthread_create(),但在内核中二者最终都归结到kernel_clone方法。 - 线程共享内存空间、文件描述符等资源,但仍有私有的数据,如线程局部存储(TLS)。
- 线程在同步机制上广泛使用
futex原语来实现互斥锁和条件变量,体现出与进程级同步不同的性能优势。
为了深入理解进程与线程的生命周期,本系列博文将通过 Linux 内核6.12与 glibc 库的源码,详细探索如下关键问题:
- 进程与线程各自如何诞生?它们的创建方式在内核和glibc库中有哪些异同?
- 内核如何调度和管理进程与线程?为何说线程调度和进程调度实际上共享同一套机制?
- 线程和进程如何实现资源共享和隔离?同步、阻塞机制又如何在其中发挥作用?
- 最终,进程和线程如何结束自己的生命周期?内核又如何回收它们各自的资源?
1.进程和线程的概念
在探讨进程与线程的区别时,人们往往更关注它们在概念和资源管理上的差异。但实际上,在 Linux 内核中,进程与线程之间的相同点远远多于不同点。虽然经典操作系统理论中,我们通常会将进程抽象为进程控制块(Process Control Block, PCB),线程抽象为线程控制块(Thread Control Block, TCB),但 Linux 内核却做了更统一、更精巧的设计:Linux 内核统一使用了一个核心的数据结构——task_struct,来同时表示进程和线程。
在 Linux 内核源码中,task_struct结构定义如下(节选部分关键字段):
struct task_struct {// 1. 进程或线程的当前状态(运行、阻塞、停止等)unsigned int __state;......// 2. 进程或线程的优先级信息,用于内核调度int prio;int static_prio;int normal_prio;unsigned int rt_priority;// 3. 进程或线程的地址空间描述符(虚拟内存管理)struct mm_struct *mm;struct mm_struct *active_mm;// 4. 进程和线程的标识(PID与线程组ID)pid_t pid;pid_t tgid;// 5. 进程或线程之间的父子、兄弟关系,用于构成进程树struct task_struct __rcu *parent;struct list_head children;struct list_head sibling;struct task_struct *group_leader;// 6. 进程或线程的文件系统上下文(根目录、当前工作目录等)struct fs_struct *fs;// 7. 进程或线程打开的文件描述符信息struct files_struct *files;// 8. 进程或线程所在的各种命名空间(网络、用户、PID等)struct nsproxy *nsproxy;......
};
通过task_struct这个统一的数据结构,Linux 内核巧妙地封装并统一管理了进程和线程所涉及的众多资源。其中存在少数字段体现出进程与线程在内核管理上的差异。理解这些差异化的成员,有助于我们更清晰地认识 Linux 如何高效而灵活地实现进程与线程的统一管理。
下面,我们着重分析能体现进程与线程差异的关键成员。
1.1 进程、线程ID
我们知道,每个进程或线程在 Linux 内核中都有一个唯一的标识 ID。在 task_struct 结构体中,两个关键字段——pid 和 tgid——用于表示进程或线程的身份。
然而,对于进程和线程而言,这两个字段的含义有所不同:
- 对于进程:
pid和tgid是相同的。即,一个普通的进程,它的pid就是tgid,代表整个进程的唯一标识。 - 对于线程:Linux 线程本质上是通过
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)方式创建的轻量级进程。在同一进程内的多个线程:- 每个线程都有自己的
pid,用于内核内部调度。 - 但它们共享相同的
tgid,即所有线程的tgid都等于主线程的pid,表示它们属于同一个线程组。
- 每个线程都有自己的
图示如下,对用户程序来说,调用getpid方法返回的其实是tgid。:

1.2 进程、线程地址空间
在 Linux 内核中,mm_struct 结构体用于表示进程的虚拟内存空间,它是进程地址空间管理的核心数据结构。每个普通进程都会有一个独立的 mm_struct,用于记录其虚拟地址空间的布局,包括代码段、数据段、堆、栈等。而对于线程,由于线程共享进程的地址空间,因此多个线程共用同一个 mm_struct。
struct mm_struct {unsigned long mmap_base; /* base of mmap area */unsigned long mmap_legacy_base; /* base of mmap area in bottom-up allocations */unsigned long start_code, end_code, start_data, end_data;//代码段及数据段的开头和结尾unsigned long start_brk, brk, start_stack;//堆内存和用户态堆栈的起始地址unsigned long arg_start, arg_end, env_start, env_end;//进程命令行参数 (argv[]) 在地址空间中的位置.......
}
进程的 mm_struct 组织了进程运行所需的所有内存资源,共同表示一个虚拟地址空间。
但是对于内核线程来说,又有不同点,由于其只工作在地址空间比较高的地方,所以不涉及对虚拟内存部分的使用,故其mm_struct是NULL。

另一个字段是active_mm,该字段表示当前任务(进程或线程)在内核态执行时所使用的地址空间。它的存在主要是为了支持内核线程,因为内核线程本身没有用户态的 mm_struct,但在某些情况下仍需要访问用户态的地址空间,比如进行 copy_from_user() 或 copy_to_user() 操作。
| 字段 | 适用于 | 作用 |
|---|---|---|
mm | 进程(普通用户态任务) | 指向该进程的 mm_struct,管理进程的地址空间 |
mm | 用户态线程 | 线程共享进程地址空间,因此 mm 指向同一 mm_struct |
mm | 内核线程 | NULL,因为内核线程没有用户态地址空间 |
active_mm | 进程 | 等同于 mm,即 active_mm == mm |
active_mm | 内核线程 | 继承上一个运行的普通进程的 mm_struct,用于访问用户地址空间 |
2.进程的创建

进程的创建中最核心的就是fork系统调用。其源代码实现如下:
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMUstruct kernel_clone_args args = {.exit_signal = SIGCHLD, // 子进程退出时发送 SIGCHLD 信号给父进程};return kernel_clone(&args);
#elsereturn -EINVAL; // 如果系统不支持 MMU,则返回错误
#endif
}
struct kernel_clone_args {u64 flags;
......
}
这里传入的args,其实是传入了一个flag选项。可选的值如下:
进程/线程创建相关标志
| 宏定义 | 值 | 作用 |
|---|---|---|
CSIGNAL | 0x000000ff | 子进程退出时向父进程发送的信号掩码(通常是 SIGCHLD) |
CLONE_VM | 0x00000100 | 共享内存地址空间(进程间不会复制 mm_struct,用于线程) |
CLONE_FS | 0x00000200 | 共享文件系统信息(chdir/chroot 影响所有线程) |
CLONE_FILES | 0x00000400 | 共享文件描述符表(多个线程可使用相同 fd) |
CLONE_SIGHAND | 0x00000800 | 共享信号处理机制(信号处理函数共享) |
CLONE_PIDFD | 0x00001000 | 创建 pidfd(进程文件描述符),可用于进程管理 |
CLONE_PTRACE | 0x00002000 | 子进程继承父进程的 ptrace 追踪状态(调试相关) |
CLONE_VFORK | 0x00004000 | 使用 vfork() 语义(子进程执行 exec() 之前,父进程阻塞) |
CLONE_PARENT | 0x00008000 | 让子进程与父进程共享相同的父进程(更改 ppid) |
CLONE_THREAD | 0x00010000 | 使子进程与父进程处于同一线程组(tgid 相同,即 pthread_create() 线程) |
Namespace(命名空间)隔离相关标志
| 宏定义 | 值 | 作用 |
|---|---|---|
CLONE_NEWNS | 0x00020000 | 新挂载命名空间(独立 mount/umount) |
CLONE_NEWCGROUP | 0x02000000 | 新 cgroup 命名空间(独立的 cgroup 控制组) |
CLONE_NEWUTS | 0x04000000 | 新 UTS(主机名)命名空间(隔离 hostname 和 domainname) |
CLONE_NEWIPC | 0x08000000 | 新 IPC(进程间通信)命名空间(System V 共享内存、消息队列、信号量独立) |
CLONE_NEWUSER | 0x10000000 | 新用户命名空间(允许非 root 用户拥有 root 权限) |
CLONE_NEWPID | 0x20000000 | 新进程 ID 命名空间(进程 PID 独立,适用于容器) |
CLONE_NEWNET | 0x40000000 | 新网络命名空间(独立 IP、网络接口、端口) |
CLONE_NEWTIME | 0x00000080 | 新时间命名空间(独立 CLOCK_REALTIME 和 CLOCK_MONOTONIC) |
但是在fork进程的时候,只传递了一个SIGCHLD,这就意味着在fork进程时,虚拟地址空间、文件系统信息、文件列表都要创建新的。而这些与命名空间隔离的都会与父进程共用。
kernel_clone方法:
pid_t kernel_clone(struct kernel_clone_args *args)
{u64 clone_flags = args->flags; // 解析 clone 标志位struct completion vfork; // 用于 vfork 机制的同步struct pid *pid; // 新进程的 PID 结构struct task_struct *p; // 新进程的 task_structint trace = 0; // Ptrace 事件类型pid_t nr; // 最终返回的子进程 PID.......p = copy_process(NULL, trace, NUMA_NO_NODE, args);//复制 task_struct及其他结构体add_latent_entropy();if (IS_ERR(p))return PTR_ERR(p);......trace_sched_process_fork(current, p);//通知调度器新进程已创建pid = get_task_pid(p, PIDTYPE_PID);//获取新进程 pid 结构体nr = pid_vnr(pid);wake_up_new_task(p);//将新任务放入就绪队列等待调度器调度return nr;
}
该方法主要调用copy_process方法生成子进程的task_struct,接着调用wake_up_new_task方法将新任务放入就绪队列等待调度器调度。其主要代码如下:
/*** copy_process - 复制当前进程,创建一个新的进程(或线程)* @pid: 指向新进程的 PID 结构体* @trace: 跟踪标志* @node: NUMA 结点* @args: 进程克隆参数*/
struct task_struct *copy_process(struct pid *pid, int trace, int node, struct kernel_clone_args *args)
{struct task_struct *p; // 指向新创建进程的 task_structconst u64 clone_flags = args->flags; // 进程克隆标志struct nsproxy *nsp = current->nsproxy; // 继承当前进程的命名空间指针......// 复制当前进程的 task_struct 结构体,为新进程分配内存p = dup_task_struct(current, node);// 复制文件描述符表,根据 clone_flags 选择共享或复制retval = copy_files(clone_flags, p, args->no_files);if (retval)goto bad_fork_cleanup_semundo;// 复制文件系统信息(当前工作目录、root 目录等)retval = copy_fs(clone_flags, p);if (retval)goto bad_fork_cleanup_files;// 复制信号处理程序(包括信号屏蔽字、处理函数)retval = copy_sighand(clone_flags, p);if (retval)goto bad_fork_cleanup_fs;// 复制信号队列和信号状态retval = copy_signal(clone_flags, p);if (retval)goto bad_fork_cleanup_sighand;// 复制虚拟内存(地址空间),如果 CLONE_VM 设置,则共享地址空间retval = copy_mm(clone_flags, p);if (retval)goto bad_fork_cleanup_signal;// 复制命名空间(PID、网络、IPC、挂载等)retval = copy_namespaces(clone_flags, p);......// 如果新进程不是 init 进程,则为其分配新的 PIDif (pid != &init_struct_pid) {pid = alloc_pid(p->nsproxy->pid_ns_for_children, args->set_tid, args->set_tid_size);if (IS_ERR(pid)) { // 检查分配 PID 是否成功retval = PTR_ERR(pid);goto bad_fork_cleanup_thread;}}......return p; // 返回新进程的 task_struct 指针
}
该方法首先调用dup_task_struct函数复制了一个新的task_struct内核对象,接着对各种核心对象进行复制处理,处理命名空间下的pid申请。
2.1 复制task_struct结构体
copy_process使用dup_task_struct复制新的task_struct内核对象:
p = dup_task_struct(current, node);
其参数current表示当前进程,即父进程;node表示NUMA架构下的某个结点。这次复制只会复制task_struct结构体本身,内部的成员仅仅是复制了指针,仍然指向父进程指针指向的,如图所示。

源代码如下:
static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{struct task_struct *tsk;......tsk = alloc_task_struct_node(node);//申请task_struct结构体对象if (!tsk)return NULL;......err = arch_dup_task_struct(tsk, orig);//复制 task_struct 结构if (err)goto free_tsk;err = alloc_thread_stack_node(tsk, node);//为新进程分配内核栈if (err)goto free_tsk;.......
}
2.2 复制files_struct
在 copy_process() 中,copy_files() 负责复制或共享 files_struct(文件描述符表),决定新进程如何管理文件描述符:
static int copy_files(unsigned long clone_flags, struct task_struct *tsk, int no_files)
{struct files_struct *oldf, *newf; /* 获取当前进程(父进程)的文件描述符表 */oldf = current->files; /* 如果当前进程没有打开任何文件(如某些后台进程),直接返回 */if (!oldf)return 0;/* 如果 no_files 置位,新进程不继承文件描述符,files 设为空 */if (no_files) {tsk->files = NULL;return 0;}/* * 如果 clone_flags 设置了 CLONE_FILES,则新进程共享父进程的 files_struct,* 仅增加 files_struct 的引用计数,而不复制文件描述符表。* 这种情况通常用于多线程(pthread),所有线程共享相同的文件描述符。*/if (clone_flags & CLONE_FILES) {atomic_inc(&oldf->count);return 0;}/* * 如果没有设置 CLONE_FILES(默认行为,fork 也在此情况),* 需要为新进程创建一个独立的 files_struct,并复制父进程的文件描述符表。*/newf = dup_fd(oldf, NULL); /* 如果复制失败,返回错误码 */if (IS_ERR(newf))return PTR_ERR(newf);/* 绑定新进程的文件描述符表 */tsk->files = newf;return 0;
}
fork() 默认不传入 CLONE_FILES,因此新进程不会共享 files_struct,而是调用 dup_fd() 复制一份新的文件描述符表:

2.3 复制file_struct
在 copy_process() 中,copy_fs() 负责在 fork() 或 clone() 时处理 文件系统相关信息,即 fs_struct。该函数决定新进程是共享还是复制父进程的文件系统信息,如当前工作目录、根目录等:
static int copy_fs(unsigned long clone_flags, struct task_struct *tsk)
{struct fs_struct *fs = current->fs; // 获取当前进程(父进程)的 fs_struct/* * 如果 clone_flags 设置了 CLONE_FS,则子进程共享父进程的 fs_struct,* 例如 `chdir()` 或 `chroot()` 会影响所有线程。*/if (clone_flags & CLONE_FS) {......./* 递增 fs_struct 的引用计数 */fs->users++;spin_unlock(&fs->lock);return 0; // 共享成功,直接返回}/* * 如果没有设置 CLONE_FS(默认行为,fork 情况),则复制 fs_struct,* 使新进程拥有独立的 fs_struct。*/tsk->fs = copy_fs_struct(fs);if (!tsk->fs)return -ENOMEM; // 复制失败,返回内存不足错误return 0;
}
fork() 默认不传入CLONE_FS,所以进入copy_fs_struct方法:
struct fs_struct *copy_fs_struct(struct fs_struct *old)
{struct fs_struct *fs = kmem_cache_alloc(fs_cachep, GFP_KERNEL);/* 如果分配失败,返回 NULL */if (fs) {fs->users = 1; // 初始化用户计数,表示该 fs_struct 仅被一个进程使用fs->in_exec = 0; // 该进程未处于 exec() 过程中spin_lock_init(&fs->lock); // 初始化锁,确保并发访问安全seqcount_spinlock_init(&fs->seq, &fs->lock); // 初始化序列锁fs->umask = old->umask; // 复制 umask(文件权限掩码)spin_lock(&old->lock); // 加锁,防止并发修改fs->root = old->root; // 复制根目录路径path_get(&fs->root); // 增加根目录引用计数,防止被释放fs->pwd = old->pwd; // 复制当前工作目录路径path_get(&fs->pwd); // 增加工作目录引用计数spin_unlock(&old->lock); // 释放锁}
}
使用了当前进程的值对新进程的umask掩码、pwd、root目录进行初始化:

2.4 复制mm_struct
在 copy_process() 中,copy_mm() 负责复制或共享进程的内存管理结构 mm_struct,即进程的虚拟地址空间。
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{struct mm_struct *mm, *oldmm;tsk->min_flt = tsk->maj_flt = 0; // 次级/主缺页次数清零tsk->nvcsw = tsk->nivcsw = 0; // 进程上下文切换计数清零
#ifdef CONFIG_DETECT_HUNG_TASKtsk->last_switch_count = tsk->nvcsw + tsk->nivcsw;tsk->last_switch_time = 0; // 记录进程最后切换时间
#endiftsk->mm = NULL; //指向进程的虚拟地址空间tsk->active_mm = NULL;//用于内核线程,没有 mm,但仍可能访问用户地址空间oldmm = current->mm;if (!oldmm)return 0;if (clone_flags & CLONE_VM) {//如果 CLONE_VM 置位(如 pthread_create() 线程)mmget(oldmm);//增加 mm_struct 引用计数mm = oldmm;//共享 mm_struct,多个线程共享地址空间}else {//如果 CLONE_VM 未置位mm = dup_mm(tsk, current->mm);//调用 dup_mm() 复制 mm_struct,子进程拥有独立地址空间if (!mm)return -ENOMEM;}tsk->mm = mm;//使子进程拥有虚拟地址空间tsk->active_mm = mm;//进程访问的活动地址空间sched_mm_cid_fork(tsk);return 0;
}
在进程创建时,CLONE_VM未置位,所以会调用dup_mm申请一个新的地址空间:
static struct mm_struct *dup_mm(struct task_struct *tsk,struct mm_struct *oldmm)
{struct mm_struct *mm;int err;mm = allocate_mm(); // 分配 mm_struct 结构体if (!mm)goto fail_nomem; // 如果分配失败,跳转到错误处理memcpy(mm, oldmm, sizeof(*mm)); // 复制 oldmm 的内容到新 mm_struct..........
}
在该方法中,通过allocate_mm申请了新的mm_struct,并且将当前进程的地址空间复制到新的mm_struct对象进行了初始化。故新进程的虚拟地址空间和当前进程的是一样的,所以父进程的虚拟地址空间中的数据,子进程都可以直接使用。

2.5 复制nsproxy
关于进程创建部分的nsproxy复制及pid的申请在该博文中详细分析过:
Linux资源隔离基础(二):namespace-CSDN博客
2.6 进入就绪队列
当copy_process方法执行完毕,新进程的task_struct对象就创建出来了,接下来内核会调用wake_up_new_task方法将该新进程加入就绪队列,等待调度。
void wake_up_new_task(struct task_struct *p)
{struct rq_flags rf; // 记录运行队列锁标志struct rq *rq; // 运行队列指针int wake_flags = WF_FORK; // 进程创建时的 wake flagraw_spin_lock_irqsave(&p->pi_lock, rf.flags); // 加锁,防止并发访问WRITE_ONCE(p->__state, TASK_RUNNING);//新进程的状态改为 TASK_RUNNING,表示可调度#ifdef CONFIG_SMP/** Fork 负载均衡(SMP 多核负载均衡)*/p->recent_used_cpu = task_cpu(p); // 记录最近使用的 CPUrseq_migrate(p); // 处理 rseq 迁移(用于线程安全的 restartable sequences)__set_task_cpu(p, select_task_rq(p, task_cpu(p), &wake_flags));//直接设置 task_struct->cpu,避免不必要的 sched_class::migrate_task_rq 调用。
#endifrq = __task_rq_lock(p, &rf); // 锁定进程的运行队列update_rq_clock(rq); // 更新运行队列时钟post_init_entity_util_avg(p); // 初始化负载追踪数据(CFS调度)activate_task(rq, p, ENQUEUE_NOCLOCK | ENQUEUE_INITIAL);//将新进程 p 加入 rqtrace_sched_wakeup_new(p); // 记录 wakeup 事件(用于性能分析)wakeup_preempt(rq, p, wake_flags);//触发调度器检查新进程 p 是否应该抢占当前运行的任务
#ifdef CONFIG_SMPif (p->sched_class->task_woken) {rq_unpin_lock(rq, &rf); // 释放 `rq` 锁p->sched_class->task_woken(rq, p); // 运行 `task_woken` 钩子rq_repin_lock(rq, &rf); // 重新锁定 `rq`}
#endiftask_rq_unlock(rq, p, &rf);
}
关于调度的部分,将在后续章节分析。【挖个坑】
3.线程的创建
在上一章节中,我们深入分析了 fork() 机制,探讨了 Linux 进程的创建过程,包括 task_struct 复制、以及 wake_up_new_task() 进程调度等关键步骤。进程创建虽然能提供独立的资源隔离,但进程间通信和资源共享的开销较大,因此,在多线程并发环境下,线程(Thread) 成为更高效的执行单元。
Linux 内核通过 clone() 系统调用实现线程创建,glibc 封装了 clone(),并提供 pthread_create() 作为标准 API,供用户态程序使用。Linux 上的大多数用户态程序都依赖 glibc 来调用内核功能,它是用户程序和内核之间的桥梁。
在很多应用程序中,线程的创建由 pthread_create() 负责,而 glibc 通过 clone() 实现线程管理:

接下来深入glibc源码和linux内核源码分析线程的创建过程,首先是**__pthread_create_2_1方法,该方法调用create_thread**方法创建线程:
static int create_thread (struct pthread *pd, const struct pthread_attr *attr,bool *stopped_start, void *stackaddr,size_t stacksize, bool *thread_ran){
....const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM| CLONE_SIGHAND | CLONE_THREAD| CLONE_SETTLS | CLONE_PARENT_SETTID| CLONE_CHILD_CLEARTID| 0);//构造 clone() 需要的 flagsTLS_DEFINE_INIT_TP (tp, pd);struct clone_args args ={.flags = clone_flags,.pidfd = (uintptr_t) &pd->tid,.parent_tid = (uintptr_t) &pd->tid,.child_tid = (uintptr_t) &pd->tid,.stack = (uintptr_t) stackaddr,.stack_size = stacksize,.tls = (uintptr_t) tp,};//组装 clone_args 结构int ret = __clone_internal (&args, &start_thread, pd);//调用 __clone_internal() 进入 clone3() 系统调用,创建新线程。if (__glibc_unlikely (ret == -1))return errno;
}
这里调用了clone系统调用,clone和fork一样,最后都会执行kernel_clone方法,只是传入的参数不同。传入了CLONE_VM、CLONE_FS、CLONE_FILES等标志,这意味着,新线程与原线程或进程共用一套mm_struct、fs_struct、files_struct。
线程创建中的不同
与上述新建进程时每个新结构体都要新建不同,新建线程时每一个结构体都与创建它的进程或线程是共用一套的,这也就体现了进程和线程的本质区别:即进程有独立的虚拟地址空间,线程是与别的任务共用。最后创建结束后就是这样:

总结
在本系列博文中,我们从 Linux 内核源码与 glibc 线程库 的角度,深入剖析了 进程和线程的创建过程,并围绕fork()、clone() 的区别展开了详细讨论。
由上分析,进程的创建调用了fork系统调用,而线程的创建调用clone系统调用,在此也可以总结一下fork和clone的区别:
| 对比项 | fork() | clone() |
|---|---|---|
| 资源共享 | 不共享,采用写时复制(COW) | 允许选择性共享资源(CLONE_*) |
| 进程 ID | 新 pid,不同于父进程 | CLONE_THREAD → 相同 tgid(线程) 未设置 CLONE_THREAD → 不同 pid(进程) |
| 地址空间 | 复制 mm_struct(COW) | CLONE_VM → 共享 mm_struct(线程) |
| 文件描述符 | 复制 files_struct | CLONE_FILES → 共享 files_struct |
| 信号处理 | 复制 sighand_struct | CLONE_SIGHAND → 共享 sighand_struct |
| 性能 | 最慢(COW 复制内存) | 比 fork() 快(可共享资源) |
| 适用场景 | 创建独立进程 | 创建线程 |
欢乐的时光总是短暂的,我们下期再见!
相关文章:
Linux 进程的一生(一):进程与线程的创建机制解析
在 Linux 操作系统中,每个任务都以「进程」的形式存在。但 Linux 下的「线程」又是什么?Linux 并没有单独定义一种全新数据结构来表示线程,而是将线程视为一种特殊的进程——一种共享资源的轻量级进程。然而,在具体实现和运行机制…...
【面试题集合】
目录 强缓存VS协商缓存**一、强缓存(本地缓存)**1. **定义**2. **核心 HTTP 头**3. **缓存生效流程**4. **应用场景** **二、协商缓存(条件请求)**1. **定义**2. **核心 HTTP 头**3. **缓存生效流程**4. **应用场景** **三、强缓存…...
【Academy】SSRF ------ Server-side request forgery
SSRF ------ Server-side request forgery 1. 什么是 SSRF?2. SSRF 攻击的影响是什么?3. 常见的 SSRF 攻击3.1 针对服务器的 SSRF 攻击3.2 针对其他后端系统的 SSRF 攻击 4. 规避常见的 SSRF 防御4.1 具有基于黑名单的输入过滤器的 SSRF4.2 具有基于白名…...
Git 的详细介绍及用法
一、Git 的优点 分布式版本控制 每个开发者都拥有完整的仓库副本,无需依赖中央服务器(如 SVN)。支持离线操作(提交、查看历史、创建分支等)。 高效的分支管理 创建和切换分支速度快(几乎是瞬间完成&#x…...
Ubuntu22.04安装数据
数据库安装步骤: sudo apt-get update sudo apt install mysql-server mysql-client sudo systemctl start mysql sudo systemctl status mysql (1)在命令行登录 MySQL 数据库,并使用 mysql 数据库 (必须使用这个…...
2025 ubuntu24系统宿主机上在线安装mysql数据库完整演示
说明:这是ubuntu24系统和安装后mysql的版本 rootmaster:/home/ubuntu# cat /etc/os-release PRETTY_NAME"Ubuntu 24.04.2 LTS" NAME"Ubuntu" VERSION_ID"24.04" VERSION"24.04.2 LTS (Noble Numbat)" VERSION_CODENAMEnob…...
STM32之I2C硬件外设
注意:硬件I2C的引脚是固定的 SDA和SCL都是复用到外部引脚。 SDA发送时数据寄存器的数据在数据移位寄存器空闲的状态下进入数据移位寄存器,此时会置状态寄存器的TXE为1,表示发送寄存器为空,然后往数据控制寄存器中一位一位的移送数…...
windows版本的时序数据库TDengine安装以及可视化工具
了解时序数据库TDengine,可以点击官方文档进行详细查阅 安装步骤 首先找到自己需要下载的版本,这边我暂时只写windows版本的安装 首先我们需要点开官网,找到发布历史,目前TDengine的windows版本只更新到3.0.7.1,我们…...
【AI】单台10卡4090 openEuler服务器离线部署kasm workspace 提供简单的GPU云服务 虚拟化桌面
下载网址 Downloads | Kasm Workspaces 文件连接 wget https://kasm-static-content.s3.amazonaws.com/kasm_release_plugin_images_amd64_1.16.1.98d6fa.tar.gz wget https://kasm-static-content.s3.amazonaws.com/kasm_release_1.16.1.98d6fa.tar.gz wget https://kasm-st…...
NetAssist 5.0.14网络助手基础使用及自动应答使用方案
以下是NetAssist v5.0.14自动应答功能的详细使用步骤: 一、基础准备: 工具下载网址页面:https://www.cmsoft.cn/resource/102.html 下载安装好后,根据需要可以创建多个server,双击程序图标运行即可,下面…...
《深度解析DeepSeek-M8:量子经典融合,重塑计算能效格局》
在科技飞速发展的今天,量子计算与经典算法的融合成为了前沿领域的焦点。DeepSeek-M8的“量子神经网络混合架构”,宛如一把钥匙,开启了经典算法与量子计算协同推理的全新大门,为诸多复杂问题的解决提供了前所未有的思路。 量子计算…...
力扣1251年
正确写法: select p.product_id, ifnull(round(sum(units*price)/sum(units),2),0) average_price from prices p left join unitssold u on u.product_idp.product_id and u.purchase_date between start_date and end_date group by p.product_id; 错误写法&a…...
【写作模板】JosieBook的写作模板
文章目录 ⭐前言⭐一、设计模式怎样解决设计问题?🌟1、寻找合适的对象✨(1)✨(2)✨(3) 🌟2、决定对象的粒度🌟3、指定对象接口🌟4、描述对象的实现🌟5、运用复用机制🌟6、关联运行时和编译时的结…...
47.HarmonyOS NEXT 登录模块开发教程(二):一键登录页面实现
温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦! HarmonyOS NEXT 登录模块开发教程(二):一键登录页面实现 文章目录 HarmonyOS NEXT 登录模块开发教程࿰…...
5.1 程序调试
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的 本节中为了演示方便,使用的代码如下: 【例 5.1】【项目:code5-001】程序的调试。 static void Ma…...
Cursor初体验:excel转成CANoe的vsysvar文件
今天公司大佬先锋们给培训了cursor的使用,还给注册了官方账号!跃跃欲试,但是测试任务好重,结合第三方工具开发也是没有头绪。 但巧的是,刚好下午有同事有个需求,想要把一个几千行的excel转成canoe的系统变…...
vue3-element-admin 前后端本地启动联调
一、后端环境准备 1.1、下载地址 gitee 下载地址 1.2、环境要求 JDK 17 1.3、项目启动 克隆项目 git clone https://gitee.com/youlaiorg/youlai-boot.git数据库初始化 执行 youlai_boot.sql 脚本完成数据库创建、表结构和基础数据的初始化。 修改配置 application-dev.y…...
emacs使用mongosh的方便工具发布
github项目地址: GitHub - csfreebird/emacs_mongosh: 在emacs中使用mongosh快速登录mongodb数据库 * 用途 在emacs中使用mongosh快速登录mongodb数据库, 操作方法: M-x mongosh, 输入数据库名称,然后就可以自动登录,前提是你已经配置好了…...
《MySQL数据库从零搭建到高效管理|库的基本操作》
目录 一、数据库的操作 1.1 展示数据库 1.2 创建数据库 1.3 使用数据库 1.4 查看当前数据库 1.5 删除数据库 1.6 小结 二、常用数据类型 2.1 数值类型 2.2 字符串类型 2.3 日期类型 一、数据库的操作 打开MySQL命令行客户端,安装完MySQL后会有两个客户端…...
Linux Shell 脚本编程极简入门指南
一、学习前提准备 ✅ 环境要求: Linux系统(Ubuntu/CentOS等)或 WSL (Windows用户) 任意文本编辑器(推荐VSCode/Vim) 基础命令行操作能力 🔍 验证环境: # 查看系统默认Shell echo $SHELL #…...
多视图几何--相机标定--从0-1理解张正友标定法
1基本原理 1.1 单应性矩阵(Homography)的建立 相机模型:世界坐标系下棋盘格平面(Z0)到图像平面的投影关系为: s [ u v 1 ] K [ r 1 r 2 t ] [ X Y 1 ] s \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} K…...
Manus 一码难求,MetaGPT、OpenManus、Camel AI 会是替代方案吗?
Manus 一码难求,MetaGPT、OpenManus、Camel AI 会是替代方案吗? 一、Manus 的现象与问题 Manus 作为一款号称“全球首个通用 AI 智能体”的产品,凭借其强大的功能和新颖的营销策略迅速走红。然而,其封闭的邀请码机制和高昂的使用…...
mac使用Homebrew安装miniconda(mac搭建python环境),并在IDEA中集成miniconda环境
一、安装Homebrew mac安装brew 二、使用Homebrew安装miniconda brew search condabrew install miniconda安装完成后的截图: # 查看是否安装成功 brew list环境变量(无需手动配置) 先执行命令看能不能正常返回,如果不能正常…...
Linux基础开发工具—vim
目录 1、vim的概念 2、vim的常见模式 2.1 演示切换vim模式 3、vim命令模式常用操作 3.1 移动光标 3.2 删除文字 3.3 复制 3.4 替换 4、vim底行模式常用命令 4.1 查找字符 5、vim的配置文件 1、vim的概念 Vim全称是Vi IMproved,即说明它是Vi编辑器的增强…...
【C++】数据结构 队列的实现
本篇博客给大家带来的是用C语言来实现数据结构的队列的实现! 🐟🐟文章专栏:数据结构 🚀🚀若有问题评论区下讨论,我会及时回答 ❤❤欢迎大家点赞、收藏、分享! 今日思想:你…...
macOS 终端优化
macOS 安装、优化、还原、升级 Oh My Zsh 完全指南 🚀 Oh My Zsh 是 macOS 终端增强的利器,它能提供强大的自动补全、主题定制和插件支持,让你的终端更高效、更炫酷。本文将全面介绍 如何安装、优化、还原、重新安装和升级 Oh My Zsh&#x…...
大语言模型-1.2-大模型技术基础
简介 本博客内容是《大语言模型》一书的读书笔记,该书是中国人民大学高瓴人工智能学院赵鑫教授团队出品,覆盖大语言模型训练与使用的全流程,从预训练到微调与对齐,从使用技术到评测应用,帮助学员全面掌握大语言模型的…...
Flutter 按钮组件 TextButton 详解
目录 1. 引言 2. TextButton 的基本用法 3. 主要属性 4. 自定义按钮样式 4.1 修改文本颜色 4.2 添加背景色 4.3 修改按钮形状和边距 4.4 样式定制 5. 高级应用技巧 5.1 图标文本组合 5.2 主题统一配置 5.3 动态交互 6. 性能优化与注意事项 6.1 点击区域优化 6.…...
Qt 数据库操作(Sqlite)
数据库简介 关于数据库的基础知识这里就不做介绍了,相关博客可以查看: SQL基础知识 数据库学霸笔记 上面博客都写的比较详细,本文主要介绍如何使用Qt进行数据库相关操作,数据库分为关系型数据库和非关系型数据,关系…...
vue 自行封装组件,类似于el-tree组件结构
背景: 接口返回是平面数组,需要经过分类处理,转成多维数组,以满足封装组件的数据结构。 有用到插件lodash。 import { groupBy, flattenDeep } from "lodash"; 效果展示: 处理数据: 对于接口返回…...
