• [技术干货] 掌控进程:深入剖析 Linux 内核调度机制-转载
    一、Linux 内核支持调度策略主要分为实时调度策略和普通(非实时)调度策略,以及一种特殊的限期调度策略。 先进先出调度(SCHED_FIFO),非抢占式的实时调度策略,没有时间片。一直占用 CPU,直到主动放弃 CPU,或者被更高优先级的实时进程抢占。 轮流调度(SCHED_RR),抢占式的实时调度策略,有时间片。 限期调度策略(SCHED_DEADLINE),Linux 3.14 引入的一种基于 Earliest Deadline First (EDF) 和 Constant Bandwidth Server (CBS) 算法的实时调度策略。 用不同的调度策略调度实时进程。普通进程支持两种调度策略: 标准轮流分时(SCHED_NORMAL):默认的调度策略,调度大多数非实时、通用目的的进程。用完全公平调度器(CFS),为所有进程提供公平的 CPU 时间分配,同时考虑进程的 nice 值。 批处理调度(SCHED_BATCH ):调度普通的非实时进程,针对 CPU 密集型、非交互式任务进行了优化。 空闲(SCHED_IDLE):最低优先级的调度策略。只有系统没有其他任何可运行的进程,SCHED_IDLE 进程才会被调度运行。   二、进程优先级限期进程的优先级比实时进程高,实时进程的优先级比普通进程高。 优先级层次: 限期进程 (SCHED_DEADLINE) 有最高的优先级,根据截止时间进行调度,在所有其他进程之前完成。 实时进程 (SCHED_FIFO, SCHED_RR) 的优先级次之,高于所有普通进程。 普通进程 (SCHED_NORMAL, SCHED_BATCH) 有相对较低的优先级。 空闲进程 (SCHED_IDLE) 有最低的优先级。 限制进程的优先级是-1。实时进程的实时优先级是 1-99,优先级数值越大,表示优先级 越高。普通进程的静态优先级是 100-139,优先级数值越小,表示优先 级越高,可通过修改 nice 值改变普通进程的优先级,优先级等于 120 加上 nice 值。 优先级数值: 限期进程:优先级由截止时间决定,截止时间越早,逻辑优先级越高。 实时进程:优先级范围是 1 到 99。数值越大,优先级越高。 普通进程:静态优先级范围是 100 到 139。数值越小,优先级越高。普通进程的优先级可以修改 nice 值来调整。nice 值的范围是 -20 到 +19。静态优先级计算公式:静态优先级 = 120 + nice 值。 空闲进程:优先级最低,内部表示为 -1。 prio是调度优先级,数值越小,优先级越高;多数情况为normal_prio。 优先级 限期进程 普通进程 实时进程 prio normal_prio normal_prio normal_prio static_prio 0 120 加上 nice 值,数值越小优先级越高。 0 normal_prio -1 static_prio 99至rt_priority rt_priority 0 0 1至99,数值越大优先级越高 在 task_struct 结构体中,4 个成员和优先级有关如下: // include/linux/sched.hint          prio;int          static_prio;int          normal_prio;unsigned int rt_priority;AI写代码三、公平调度 CFS 与其它调度3.1、调度类Linux 内核 sched_class 调度器有五种类型: dl_sched_class:限期调度类。 rt_sched_class:实时调度类。 stop_sched_class:停机调度类。 idle_sched_c lass。 fair_sched_class。 其中每种调度类都有自己的调度策略。主要是为方便添加新的调度策略 ,Linux 内核抽象一 个调 度类 sched_class。其调试器类型源码如下(kernel/sched/sched.h): extern const struct sched_class dl_sched_classextern const struct sched_class rt_sched_classextern const struct sched_class stop_sched_classextern const struct sched_class idle_sched_c lassextern const struct sched_class fair_sched_classAI写代码stop_sched_class调度类可以抢占其他进程,其他进程不能抢占它;停机调度类是指将处理器停下来做更加紧急的工作,只有迁移线程属于停机调度类,每个CPU有一个迁移线程cpu_id。 dl_sched_class调度类使用红黑树将进程按照绝对截至限期从小到大排序,每次调度的时间选择绝对截止限期最小的进程。 rt_sched_class将每个调度优先级维护一个队列,通过位图bitmap能够快速查找第一个非空队列;DECLARE_BITMAP(bitmap,MAX_RT_PRIO+1)。 调度类优先级,由高到低进行排列:停机调度类>限期调度类> 实时调度类>公平调度类>空闲调度类。 修改时间片(默认是5ms):/proc/sys/kernel/sched_rt_timeslice_ms 3.2、公平调度类 CFS公平调度类应用完全公平调度算法,丢掉时间片和固定调度周 期,在此引入虚拟运行时间。vruntime 的计算公式如下: 虚拟运行时间(vruntime)=实际运行时间*nice0 权重值/进程权重值。 /* * Nice levels are multiplicative, with a gentle 10% change for every * nice level changed. I.e. when a CPU-bound task goes from nice 0 to * nice 1, it will get ~10% less CPU time than another CPU-bound task * that remained on nice 0. * * The "10% effect" is relative and cumulative: from _any_ nice level, * if you go up 1 level, it's -10% CPU usage, if you go down 1 level * it's +10% CPU usage. (to achieve that we use a multiplier of 1.25. * If a task goes up by ~10% and another task goes down by ~10% then * the relative distance between them is ~25%.) */const int sched_prio_to_weight[40] = { /* -20 */     88761,     71755,     56483,     46273,     36291, /* -15 */     29154,     23254,     18705,     14949,     11916, /* -10 */      9548,      7620,      6100,      4904,      3906, /*  -5 */      3121,      2501,      1991,      1586,      1277, /*   0 */      1024,       820,       655,       526,       423, /*   5 */       335,       272,       215,       172,       137, /*  10 */       110,        87,        70,        56,        45, /*  15 */        36,        29,        23,        18,        15,};AI写代码 完全公平调度算法使用红黑树将进程按照虚拟运行时间从小到大排序,每次调度的时候选择虚拟运行时间最小的那个进程。 进程时间片=调度周期*进程权重 / 运行队列中全部进程的权重之和。 3.3、运行队列每个处理器有一个运行队列,结构体是 rq,定义的全局变量如下: DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);AI写代码rq 是描述就绪队列,其设计是为每一个 CPU 就绪队列,本地进程在 本地队列上排序。 当一个进程被调度到某个处理器上运行时,它会被添加到该处理器的运行队列的末尾。当处理器空闲时,它会从运行队列的头部选择一个进程来运行。 每个运行队列都有一个时间片,这是进程在该处理器上运行之前允许运行的最大时间量。时间片通常为 10 毫秒,但可以根据需要进行配置。 当一个进程用完它的时间片时,它会被移到运行队列的末尾。如果运行队列中还有其他进程,则处理器会从运行队列的头部选择下一个进程来运行。 如果运行队列为空,则处理器将进入空闲状态。当新的进程被调度到该处理器上运行时,处理器将从空闲状态唤醒。 通过使用每个处理器自己的运行队列,Linux 内核可以确保每个处理器上的进程公平地获得 CPU 时间。它还可以防止单个进程独占一个处理器,从而导致其他进程饥饿。 3.4、调度进程主动调度进程的函数是 schedule() ,它会把主要工作委托给 __schedule()去处理。 asmlinkage __visible void __sched schedule(void){struct task_struct *tsk = current;//获取当前进程 sched_submit_work(tsk);//防止进程睡眠时发送死锁do {preempt_disable();//关闭内核抢占__schedule(false);//执行调度的核心函数处理细节sched_preempt_enable_no_resched();//开启内核抢占} while (need_resched());sched_update_worker(tsk);}EXPORT_SYMBOL(schedule);AI写代码 函数__shcedule 的主要处理过程如下: 调用 pick_next_task()以选择下一个进程。 调用 context_switch()以切换进程。 /* * __schedule() is the main scheduler function. * * The main means of driving the scheduler and thus entering this function are: * *   1. Explicit blocking: mutex, semaphore, waitqueue, etc. * *   2. TIF_NEED_RESCHED flag is checked on interrupt and userspace return *      paths. For example, see arch/x86/entry_64.S. * *      To drive preemption between tasks, the scheduler sets the flag in timer *      interrupt handler scheduler_tick(). * *   3. Wakeups don't really cause entry into schedule(). They add a *      task to the run-queue and that's it. * *      Now, if the new task added to the run-queue preempts the current *      task, then the wakeup sets TIF_NEED_RESCHED and schedule() gets *      called on the nearest possible occasion: * *       - If the kernel is preemptible (CONFIG_PREEMPTION=y): * *         - in syscall or exception context, at the next outmost *           preempt_enable(). (this might be as soon as the wake_up()'s *           spin_unlock()!) * *         - in IRQ context, return from interrupt-handler to *           preemptible context * *       - If the kernel is not preemptible (CONFIG_PREEMPTION is not set) *         then at the next: * *          - cond_resched() call *          - explicit schedule() call *          - return from syscall or exception to user-space *          - return from interrupt-handler to user-space * * WARNING: must be called with preemption disabled! */static void __sched notrace __schedule(bool preempt){struct task_struct *prev, *next;unsigned long *switch_count;struct rq_flags rf;struct rq *rq;int cpu; cpu = smp_processor_id();rq = cpu_rq(cpu);prev = rq->curr; schedule_debug(prev, preempt); if (sched_feat(HRTICK))hrtick_clear(rq); local_irq_disable();rcu_note_context_switch(preempt); /** Make sure that signal_pending_state()->signal_pending() below* can't be reordered with __set_current_state(TASK_INTERRUPTIBLE)* done by the caller to avoid the race with signal_wake_up().** The membarrier system call requires a full memory barrier* after coming from user-space, before storing to rq->curr.*/rq_lock(rq, &rf);smp_mb__after_spinlock();/* Promote REQ to ACT */rq->clock_update_flags <<= 1;update_rq_clock(rq);switch_count = &prev->nivcsw;if (!preempt && prev->state) {if (signal_pending_state(prev->state, prev)) {prev->state = TASK_RUNNING;} else {deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);if (prev->in_iowait) {atomic_inc(&rq->nr_iowait);delayacct_blkio_start();}}switch_count = &prev->nvcsw;}next = pick_next_task(rq, prev, &rf);clear_tsk_need_resched(prev);clear_preempt_need_resched();if (likely(prev != next)) {rq->nr_switches++;/** RCU users of rcu_dereference(rq->curr) may not see* changes to task_struct made by pick_next_task().*/RCU_INIT_POINTER(rq->curr, next);/** The membarrier system call requires each architecture* to have a full memory barrier after updating* rq->curr, before returning to user-space.** Here are the schemes providing that barrier on the* various architectures:* - mm ? switch_mm() : mmdrop() for x86, s390, sparc, PowerPC.*   switch_mm() rely on membarrier_arch_switch_mm() on PowerPC.* - finish_lock_switch() for weakly-ordered*   architectures where spin_unlock is a full barrier,* - switch_to() for arm64 (weakly-ordered, spin_unlock*   is a RELEASE barrier),*/++*switch_count;trace_sched_switch(preempt, prev, next);/* Also unlocks the rq: */rq = context_switch(rq, prev, next, &rf);} else {rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);rq_unlock_irq(rq, &rf);}balance_callback(rq);}/* * context_switch - switch to the new MM and the new thread's register state. */static __always_inline struct rq *context_switch(struct rq *rq, struct task_struct *prev,       struct task_struct *next, struct rq_flags *rf){prepare_task_switch(rq, prev, next); /** For paravirt, this is coupled with an exit in switch_to to* combine the page table reload and the switch backend into* one hypercall.*/arch_start_context_switch(prev); /** kernel -> kernel   lazy + transfer active*   user -> kernel   lazy + mmgrab() active** kernel ->   user   switch + mmdrop() active*   user ->   user   switch*/if (!next->mm) {                                // to kernelenter_lazy_tlb(prev->active_mm, next); next->active_mm = prev->active_mm;if (prev->mm)                           // from usermmgrab(prev->active_mm);elseprev->active_mm = NULL;} else {                                        // to usermembarrier_switch_mm(rq, prev->active_mm, next->mm);/** sys_membarrier() requires an smp_mb() between setting* rq->curr / membarrier_switch_mm() and returning to userspace.** The below provides this either through switch_mm(), or in* case 'prev->active_mm == next->mm' through* finish_task_switch()'s mmdrop().*/switch_mm_irqs_off(prev->active_mm, next->mm, next);if (!prev->mm) {                        // from kernel/* will mmdrop() in finish_task_switch(). */rq->prev_mm = prev->active_mm;prev->active_mm = NULL;}}rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);prepare_lock_switch(rq, next, rf);/* Here we just switch the register state and the stack. */switch_to(prev, next, prev);barrier();return finish_task_switch(prev);}AI写代码 (1)切 换 用 户 虚 拟 地 址 空 间 , ARM64 架 构 使 用 默 认 的switch_mm_irqs_off,其内核源码定义如下: // include/linux/mmu_context.h /* SPDX-License-Identifier: GPL-2.0 */#ifndef _LINUX_MMU_CONTEXT_H#define _LINUX_MMU_CONTEXT_H #include <asm/mmu_context.h> struct mm_struct; void use_mm(struct mm_struct *mm);void unuse_mm(struct mm_struct *mm); /* Architectures that care about IRQ state in switch_mm can override this. */#ifndef switch_mm_irqs_off# define switch_mm_irqs_off switch_mm#endif #endifAI写代码 switch_mm 函数内核源码处理如下: // arch/arm64/include/asm/mmu_context.h static inline void __switch_mm(struct mm_struct *next){unsigned int cpu = smp_processor_id(); /** init_mm.pgd does not contain any user mappings and it is always* active for kernel addresses in TTBR1. Just set the reserved TTBR0.*/if (next == &init_mm) {cpu_set_reserved_ttbr0();return;} check_and_switch_context(next, cpu);} static inline voidswitch_mm(struct mm_struct *prev, struct mm_struct *next,  struct task_struct *tsk){if (prev != next)__switch_mm(next); /** Update the saved TTBR0_EL1 of the scheduled-in task as the previous* value may have not been initialised yet (activate_mm caller) or the* ASID has changed since the last run (following the context switch* of another thread of the same process).*/update_saved_ttbr0(tsk, next);}AI写代码 (2)切 换 寄 存 器 , 宏 switch_to 把 这 项 工 作 委 托 给 函 数__switch_to: // include/asm-generic/switch_to.h #ifndef __ASM_GENERIC_SWITCH_TO_H#define __ASM_GENERIC_SWITCH_TO_H #include <linux/thread_info.h> /* * Context switching is now performed out-of-line in switch_to.S */extern struct task_struct *__switch_to(struct task_struct *,       struct task_struct *); #define switch_to(prev, next, last) \do { \((last) = __switch_to((prev), (next))); \} while (0) #endif /* __ASM_GENERIC_SWITCH_TO_H */(arch/arm64/kernel/process.c) /* * Thread switching. */__notrace_funcgraph struct task_struct *__switch_to(struct task_struct *prev,struct task_struct *next){struct task_struct *last; fpsimd_thread_switch(next);tls_thread_switch(next);hw_breakpoint_thread_switch(next);contextidr_thread_switch(next);entry_task_switch(next);uao_thread_switch(next);ptrauth_thread_switch(next);ssbs_thread_switch(next); /** Complete any pending TLB or cache maintenance on this CPU in case* the thread migrates to a different CPU.* This full barrier is also required by the membarrier system* call.*/dsb(ish); /* the actual thread switch */last = cpu_switch_to(prev, next); return last;}AI写代码 3.5、调度时机调度进程的时机: 进程主动调用 schedule()函数。 周期性地调度,抢占当前进程,强迫当前进程让出处理器。 唤醒进程的时候,被唤醒的进程可能抢占当前进程。 创建新进程的时候,新进程可能抢占当前进程。 (1)主动调度 :进程在用户模式下运行,无法直接调用 schedule()函数,只能通过系统调用进入内核模式,如果系统调用需要等待某个资源,如互斥锁或信号量,就会把进程的状态设置为睡眠状态,然后调用schedule()函数来调度进程。 (2)周期调度 :周期调度的函数为 scheduler_tick(),调用当前进程所属调度类task_tick()方法。 四、RCU机制与内存屏障(1)RCU(read-copy update)为 Linux 当中的一种同步机制,则为读/拷贝更新。 写者修改对象流程:先复制生成一个副本,然后更新这个副本,最后使用新的对象替换旧的对象,在写者执行复制的时候读者则执行可以读数据。 写者删除对象时,必须等待所有访问对象的访问者都访问结束了才能删除对象。等待所有读者访问结束的时间叫做宽期限。 RCU的读者没有任何的同步开销,不需要获取任何的锁,也不需要执行延迟指令,也不需要执行内存屏障;但是写者的同步开销非常大,需要延迟对象释放时间,复制被修改的对象,写者直接必须使用锁;从某个意义来说也算是RCU的缺点。 (2)内存屏障 :保证内存访问的顺序方法,用来解决编译器编码的时候可能会重新排序汇编指令。 因为为了使编译出来的程序在CPU上运行更快,有时候优化结果可能不符合程序的要求;现在的CPU采用超标量的一个体系结构和lanch技术,能够一个时钟并行执行很多条指令。 内存屏障可分为两种类型:编译器内存屏障和 CPU 内存屏障。 内核支持三种内存屏障:内存映射I/O写屏障、编译器屏障、处理器屏障。 五、总结Linux 内核调度机制是复杂、强大的系统,对进程行为进行精细的控制。理解进程优先级、调度策略和调度类,可以优化系统满足特定应用程序和工作负载的需求。————————————————                             版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/mr_yuanshen/article/details/152028702
  • [技术干货] 【Linux】Linux文件系统详解:从磁盘到文件的奥秘 -转载
    1. 初识文件系统:硬盘的"整理术"想象你有一个巨大的仓库(硬盘),要存放各种箱子(文件)。如果随便堆放在一起,找东西会很麻烦。文件系统就是给仓库画格子、贴标签的"整理术",让每个文件都有自己的位置和编号。1.1 分区:给仓库画格子硬盘需要先分成多个分区(类似仓库的不同区域),每个分区独立管理。分区后还需要格式化——写入文件系统的"管理规则",包括:数据块(存放文件内容的4KB小格子)inode(文件的身份证,记录属性和数据块位置)超级块(整个分区的"户口本")1.2 数据块:文件内容的"集装箱"所有文件内容都存在数据块(Data Blocks)中,每个块固定4KB大小。小文件可能占1块,大文件会占用多个块,这些块可以分散在磁盘的不同位置。 2. inode:文件的"身份证"每个文件都有一个inode(索引节点),相当于"身份证",包含:文件大小、权限、创建时间等属性指向数据块的指针(告诉系统去哪里找内容)2.1 inode的关键特性唯一编号:每个分区内inode编号唯一,类似身份证号跨区编号:inode和数据块在整个分区内统一编号(如1-10000)不能跨分区:inode编号只在本分区有效,就像小区门牌号不能跨小区使用2.2 查看inode的命令ls -i filename  # 查看文件的inode编号stat filename   # 查看inode详细属性执行效果:12345 filename  # ls -i输出,12345是inode编号3. 目录:文件名的"通讯录"你可能会问:文件名存在哪里?答案是:目录里。目录本身也是一种特殊文件,它的inode指向的数据块中,存储着"文件名→inode编号"的映射表,就像通讯录记录"姓名→电话"。3.1 目录的inode和数据块目录的数据块内容示例:. → inode 100  (当前目录自身).. → inode 50   (父目录)file1.txt → inode 12345doc/ → inode 678903.2 路径解析:从"/"到文件找文件的过程就像查地图:从根目录(/)开始,根目录的inode编号是固定的(通常是2)逐层解析路径(如/home/user/file.txt)每个目录的"通讯录"找到下一级目录/文件的inode最终通过目标文件的inode找到数据块4. 超级块(Super Block):文件系统的"户口本"超级块存储整个分区的关键信息:总块数、空闲块数、inode总数块大小、inode大小、挂载时间4.1 为什么需要备份超级块?超级块一旦损坏,整个分区的数据可能丢失!因此系统会在多个分组中备份超级块,就像重要文件多存几份副本。 4.2 查看超级块信息dumpe2fs /dev/sda1 | grep -i superblock  # 查看ext系列文件系统的超级块5. 挂载:给分区"安个门牌号"硬盘分区就像未开封的快递箱,需要挂载到一个目录(挂载点)才能使用,这个目录就成了分区的"门牌号"。5.1 挂载步骤示例# 1. 创建挂载点目录mkdir /mnt/mydisk# 2. 将/dev/sdb1分区挂载到/mnt/mydiskmount /dev/sdb1 /mnt/mydisk# 3. 查看挂载情况df -h          # 显示分区使用情况5.2 开机自动挂载修改/etc/fstab文件(需root权限):# 格式:设备路径  挂载点  文件系统类型  选项  备份  自检/dev/sdb1       /mnt/mydisk  ext4    defaults  0     2AI写代码bash126. 软硬链接:文件的"分身术"Linux有两种链接方式,就像文件的不同分身术:6.1 软链接(符号链接):快捷方式相当于Windows的"快捷方式",有独立inode内容存储目标文件的路径,删除原文件后链接失效ln -s 原文件路径 软链接名  # 创建软链接ln -s /home/user/file.txt link.txt6.2 硬链接:文件别名没有独立inode,只是给原文件新增一个文件名(共享inode)原文件删除后,别名仍可访问(需引用计数>0)ln 原文件路径 硬链接名  # 创建硬链接ln file.txt hardlink.txt6.3 链接数的奥秘文件的"链接数"就是指向该inode的文件名数量目录默认链接数是2(.和..),创建子目录后会增加ls -l  # 查看链接数(第2列数字)# 示例输出:drwxr-xr-x  2 user user 4096  目录(链接数2)7. 内核的小优化:dentry缓存操作系统为了加快文件查找速度,会把常用目录的"文件名→inode"映射缓存到内存中,这就是dentry结构体(目录项缓存)。作用:第二次查找同一目录时,直接从内存读取,无需访问磁盘验证:find命令第一次慢、第二次快,就是因为dentry缓存生效总结:文件系统的核心逻辑存储三要素:分区(容器)、inode(身份证)、数据块(内容箱)查找流程:路径→目录→inode→数据块关键技术:挂载(分区入门)、链接(文件分身)、缓存(加速访问)通过这些机制,Linux能高效管理成千上万的文件,即使你每天用ls、cd命令,背后都藏着这些精妙的设计!1.3 磁盘物理结构与分区表硬盘的物理结构包括盘片、磁道和扇区,每个扇区通常为512字节。分区是将磁盘划分为独立区域的过程,常见的分区表有两种:MBR分区表:最多支持4个主分区每个分区最大容量2TB位于磁盘的第一个扇区(512字节)GPT分区表:支持无限多个分区(取决于操作系统,通常256个)支持大于2TB的分区有备份分区表,提高可靠性使用gdisk工具创建GPT分区表的界面,支持大磁盘和多分区实战代码:创建GPT分区# 查看磁盘信息fdisk -l /dev/sdb# 使用gdisk创建GPT分区表gdisk /dev/sdb# 按提示操作:o(新建GPT)→ n(新建分区)→ 回车(默认起始扇区)→ +10G(分区大小)→ w(保存)2.3 inode的15个指针inode包含15个指针,决定了文件能使用的数据块:前12个直接指针:每个指向一个数据块(4KB),直接访问小文件(≤48KB)一级间接指针:指向一个"指针块",可存储1024个数据块地址(4KB/4B=1024),支持4MB二级间接指针:指向一个"指针块的指针块",支持4GB三级间接指针:指向一个"二级指针块的指针块",支持4TBinode的多级指针结构,使小文件快速访问,大文件无限扩展计算最大文件大小:block_size = 4096  # 4KB/块direct = 12 * block_sizesingle = (block_size // 4) * block_size  # 1024块double = (block_size // 4) ** 2 * block_size  # 1024²块triple = (block_size // 4) ** 3 * block_size  # 1024³块max_size = direct + single + double + tripleprint(f"最大文件大小: {max_size / (1024**4):.2f} TB")  # 输出:4.00 TB3.3 块组的6大组成部分每个块组就像一个独立的"小区",包含:Super Block:小区总览图GDT(组描述符表):每个块组的详细信息inode位图:记录哪些inode已使用数据块位图:记录哪些数据块已使用inode表:存储inode的具体内容数据块:实际存储文件内容块组的6个组成部分,每个部分负责不同的管理功能空闲块管理:数据块位图使用1位表示一个块的状态:0:空闲(可用)1:已占用例如,一个4KB的位图可以管理32768个数据块(4KB×8=32768位),轻松定位空闲块。4.3 目录项缓存(dentry cache)Linux内核为加速路径解析,将常用目录项缓存在内存中,称为dentry缓存:结构:哈希表+链表,支持快速查找和LRU淘汰命中率:缓存命中率高时,文件访问速度显著提升dentry结构体包含指向inode的指针和目录项关系验证缓存存在:# 第一次执行,无缓存,较慢time find / -name "passwd"# 第二次执行,利用缓存,较快time find / -name "passwd"5.3 VFS:虚拟文件系统VFS是Linux的"文件系统翻译官",使不同文件系统(ext4、xfs等)呈现统一接口:核心对象:超级块对象、inode对象、文件对象、目录项对象作用:用户无需关心底层文件系统类型,统一使用open/read/write等系统调用VFS位于用户空间和具体文件系统之间,提供统一接口安全挂载选项:# 以只读方式挂载U盘,防止病毒写入mount -o ro /dev/sdb1 /mnt/usb# 禁止在分区上执行程序,提高安全性mount -o noexec /dev/sdb1 /mnt/usb6.3 硬链接数实验创建硬链接后,inode的链接数会增加:# 创建文件,初始链接数1touch file.txtls -li file.txt  # 输出:12345 -rw-r--r-- 1 user user ... file.txt# 创建硬链接,链接数变为2ln file.txt link.txtls -li file.txt link.txt  # 两者inode相同,链接数2# 删除原文件,链接数变为1,link.txt仍可访问rm file.txtcat link.txt  # 仍能读取内容硬链接创建后,链接数从1变为2,删除原文件后链接数减为17.1 Super Block损坏修复Super Block损坏会导致分区无法挂载,可使用备份恢复:# 查看分区的超级块备份位置mke2fs -n /dev/sda1  # 模拟格式化,显示超级块备份信息# 使用备份超级块修复e2fsck -b 32768 /dev/sda1  # 32768是备份超级块的块地址Super Block在多个块组中备份,确保损坏后可恢复————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/wheeldown/article/details/151972370
  • [技术干货] linux——自定义协议 -转载
    什么是自定义协议?自定义协议 是指根据应用程序的需求,开发者定义的一种数据格式或传输规则。在网络通信中,协议用于描述双方通信时的数据结构、数据格式、传输顺序等。定义:自定义协议是开发者为特定应用或场景设计的协议,用来满足应用之间的数据交换需求。与标准协议(如HTTP、TCP/IP)不同,自定义协议不依赖于任何标准,而是根据具体需求自行设计。用途:例如,在一个客户端与服务器之间进行通信时,客户端发送特定格式的数据请求,服务器根据预定义的协议格式解析并返回响应数据。自定义协议的设计通常需要考虑数据的序列化、反序列化、传输效率和安全性。示例:比如,设计一个简单的数学计算协议,客户端发送请求(比如两个数字和一个运算符),服务器进行处理,返回结果。自定义协议在此场景中的格式可以是:数字1 运算符 数字2。// 请求结构struct Request {    int num1;    int num2;    char op; // 操作符(如 +,-,*,/)}; // 响应结构struct Response {    int result;    int code; // 0: 成功,1: 错误};AI写代码cpp运行通信双方根据这个格式进行数据的传递和解析,即构成了自定义协议.自定义协议 是属于 应用层 的概念。应用层 是 OSI 模型中的最上层,也是计算机网络协议栈中最接近用户的层。它定义了应用程序之间如何交换数据,涵盖了各种应用协议,如 HTTP、FTP、SMTP,以及你提到的自定义协议。自定义协议是应用层的一部分,通常由应用程序开发者定义,用于特定应用的需求。自定义协议可以定义数据格式、通信规则、如何打包和解包数据等。它通常使用其他协议(如 TCP/IP、UDP)来实现数据的传输。说到自定义协议,我们还要谈到序列化和反序列化这两个概念。序列化与反序列化简介序列化 和 反序列化 是计算机程序在处理数据传输和存储时常用的两个概念。它们用于将数据从一种格式转化为另一种格式,通常在网络通信、文件存储和跨平台数据交换中广泛应用。1. 序列化(Serialization)序列化 是将程序中的对象、数据结构或内存中的数据转换为可存储或传输的格式的过程。常见的格式有 JSON、XML、二进制数据流等。目的:将内存中的复杂数据结构转换为字节流或其他格式,使其能够被存储在文件、数据库中或通过网络传输。应用场景:网络通信:将对象或数据结构转化为字节流或字符串,在客户端与服务器之间传输。数据持久化:将对象序列化为文件或数据库记录,便于后续读取。跨平台通信:将数据转换为跨平台通用格式,支持不同系统之间的通信。2. 反序列化(Deserialization)反序列化 是将序列化后的数据(如字节流或字符串)转换回程序能够理解的对象、数据结构或实体的过程。目的:将传输或存储的字节流转换回原始数据结构,使程序可以对其进行操作。应用场景:网络通信:将接收到的数据字节流还原为原始对象,供程序进一步处理。数据恢复:从存储中恢复对象数据,供程序重新使用。将数据转化为字节流而不是直接传输结构体本身,主要是出于以下几个原因:跨平台兼容性:不同平台的内存布局差异会导致直接传输结构体时的兼容性问题。灵活性:序列化为字节流或标准化格式,可以提高程序的灵活性、可扩展性,并支持版本控制。网络传输效率:通过序列化,你可以优化数据的大小,减少带宽浪费,并更容易适应不同的网络协议。调试与可读性:序列化格式(如 JSON)更加易于调试和查看,对于错误处理和日志记录也更为方便。安全性:序列化后的数据更容易进行验证,保证数据的完整性和安全性。TCP的全双工通信总结全双工通信 是指通信双方可以同时进行数据的发送和接收。在 TCP(传输控制协议)中,全双工通信 是其核心特性之一,支持客户端和服务器之间在同一个连接上同时发送和接收数据。 TCP全双工通信的关键要点:独立的发送与接收缓冲区:每一端(客户端和服务器)都有两个独立的缓冲区:一个用于发送数据(发送缓冲区),另一个用于接收数据(接收缓冲区)。发送和接收操作互不干扰,可以同时进行。同时发送和接收数据:在一个TCP连接上,客户端和服务器可以同时发送和接收数据。例如,客户端可以在发送请求的同时接收服务器的响应,服务器也可以在接收客户端请求时,立即返回数据。数据流双向传输:TCP连接中的数据流是双向的。每个方向上都可以独立进行数据传输,而不会相互阻塞。基于缓冲区的管理:发送数据时,数据从应用层的缓冲区拷贝到 发送缓冲区,然后通过网络传输给接收方。接收方的数据从网络传输到接收缓冲区,应用层可以从接收缓冲区读取数据。这两个缓冲区(发送缓冲区和接收缓冲区)的独立性保证了全双工通信的流畅性。流量控制与拥塞控制:TCP采用 流量控制 和 拥塞控制,确保在通信过程中,双方的发送速率匹配,避免数据丢失或网络拥塞。阻塞I/O操作:TCP的I/O函数(如 read 和 write)通常是阻塞的,确保数据的同步处理。发送和接收操作在每个缓冲区内独立进行,但为了避免数据冲突或丢失,它们通过阻塞机制进行同步。网络版计算器例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.约定方案一:客户端发送一个形如"1+1"的字符串;这个字符串中有两个操作数, 都是整形;两个数字之间会有一个字符是运算符, 运算符只能是 + ;数字和运算符之间没有空格;约定方案二:定义结构体来表示我们需要交互的信息;发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;这个过程叫做 "序列化" 和“反序列化”。1.Socket.hpp#pragma once #include <iostream>#include <string>#include <stdint.h>#include <unistd.h>#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>#include "log.hpp" const int backlog =5;extern Log lg; enum{    SOCKERR = 1,    BINDERR = 2,    LISTENERR}; class Sock{public:    Sock()    {    }    ~Sock()    {    } public:    void Socket()    {        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);        if (sockfd_ < 0)        {            lg("fatal", "socket create fail errno:%d strerrno:%s", errno, strerror(errno));            exit(1);        }    }     void Bind(uint16_t port)    {        struct sockaddr_in local;        memset(&local, 0, sizeof(local));         local.sin_family = AF_INET;        local.sin_port = htons(port);        local.sin_addr.s_addr=INADDR_ANY;         socklen_t len = sizeof(local);        if (bind(sockfd_, (struct sockaddr *)&local, len) < 0)        {            lg("fatal", "bind fail errno:%d,strerror:%s\n", errno, strerror(errno));            exit(BINDERR);        }    }     void Listen() // 变为监听窗口,错了就别玩了    {        if (listen(sockfd_, backlog) < 0)        {            lg("fatal", "Listen fail errno:%d,strerror:%s\n", errno, strerror(errno));            exit(LISTENERR);        }    }     int Accpect(string*ip,uint16_t* port)//输出型参数,谁链接的我,对方的端口,ip    {         struct sockaddr_in temp;        socklen_t len=sizeof(temp);                int newfd=accept(sockfd_,(struct sockaddr*)&temp,&len);        if(newfd<0)        {            lg("fatal", "accpect fail errno:%d,strerror:%s\n", errno, strerror(errno));            return -1;        }                *port=ntohs(temp.sin_port);                string ipstr=inet_ntoa(temp.sin_addr);        *ip=ipstr;         return newfd;    }     bool Connect(string& ip,uint16_t port)//服务器的端口,ip    {        struct sockaddr_in server;        bzero(&server,0);        server.sin_family=AF_INET;        server.sin_port=htons(port);        server.sin_addr.s_addr=inet_addr(ip.c_str());         int ret=connect(sockfd_,(struct sockaddr*)&server,sizeof(server));        if(ret<0)        {            lg("info", "Connect fail errno:%d,strerror:%s\n", errno, strerror(errno));            return false;        }        return true;    }     void Close()    {        close(sockfd_);    }public:    int sockfd_;};2.Protocal.hpp#pragma once #include <iostream>#include <string>#include <jsoncpp/json/json.h> // #define Myself 1   //编译的时候可以 用-DMyself using namespace std; const string blank_sep = " ";const string protocal_sep = "\n"; //"x op y" ->//"len"/n"x op y"/nstd::string Encode(string &content){    int len = content.size();    string package = to_string(len);    package += protocal_sep;    package += content;    package += protocal_sep;    return package;} //"len"/n"x op y"/n ->  解析可能错误//"x op y"bool Decode(string &package, string *content){    int pos = package.find(protocal_sep);    if (pos == string::npos)        return false;     string len_str = package.substr(0, pos);    int len = stoi(len_str);     int to_len = len_str.size() + 2 + len;    if (package.size() < to_len)        return false;     *content = package.substr(pos + 1, len); // 位置 加 长度     package.erase(0, to_len); // 解析的这一段,从缓冲区里去掉    return true;} class Request{public:    Request(int x, int y, char op) : x_(x), y_(y), op_(op)    {    }     Request()    {    }     ~Request()    {    } public:    bool Serialize(string *content) // 序列化为 字节流    {#ifdef Myself        string s = to_string(x_);        s += blank_sep;        s += op_;        s += blank_sep;        s += to_string(y_);         *content = s;        return true;#else        Json::Value root;         root["x"] = x_;        root["y"] = y_;        root["op"] = op_;         Json::FastWriter w;        *content = w.write(root);        return true;#endif    }     bool Deserialize(string &in) // 反序列化 将字节流解析    {#ifdef Myself        int l = in.find(blank_sep);        if (l == string::npos)            return false;        string x = in.substr(0, l); // 左闭,右开         int r = in.rfind(blank_sep);        if (r == string::npos)            return false;        string y = in.substr(r + 1);         if (l + 2 != r)            return false;         x_ = stoi(x);        y_ = stoi(y);        op_ = in[l + 1];        return true;#else        Json::Reader R;        Json::Value root;         bool ret = R.parse(in.c_str(), root);        if (!ret)            return false;        x_ = root["x"].asInt();        y_ = root["y"].asInt();        op_ = root["op"].asInt();        return true; #endif    }     void Printf()    {        cout << "任务请求" << x_ << op_ << y_ << "=?" << endl;    } public:    int x_;    int y_;    char op_;}; class Respone{public:    Respone(int res, int c) : result_(res), code_(c)    {    }     Respone()    {    }    ~Respone()    {    } public:    bool Serialize(string *out) // 序列化 形成字节流    {#ifdef Myself        string s = to_string(result_);        s += blank_sep;        s += to_string(code_);        *out = s;        return true;#else        Json::Value root;         root["result"] = result_;        root["code"] = code_;         Json::FastWriter w;        *out = w.write(root);        return true;#endif    }     bool Deserialize(string &in) // 反序列化 字节流解析    {#ifdef Myself        int pos = in.find(blank_sep);        if (pos == string::npos)            return false;        string x = in.substr(0, pos);        int result = stoi(x);         string y = in.substr(pos + 1);        int code = stoi(y);         result_ = result;        code_ = code;        return true;#else        Json::Reader R;        Json::Value root;         bool ret = R.parse(in.c_str(), root);        if (!ret)            return false;        result_=root["result"].asInt();        code_=root["code"].asInt();        return true;#endif    }     void Printf()    {        cout << "result " << result_ << "  code " << code_ << endl;    } public:    int result_;    int code_;};3.Server.cal#pragma once #include <iostream>#include <string>#include <jsoncpp/json/json.h> // #define Myself 1   //编译的时候可以 用-DMyself using namespace std; const string blank_sep = " ";const string protocal_sep = "\n"; //"x op y" ->//"len"/n"x op y"/nstd::string Encode(string &content){    int len = content.size();    string package = to_string(len);    package += protocal_sep;    package += content;    package += protocal_sep;    return package;} //"len"/n"x op y"/n ->  解析可能错误//"x op y"bool Decode(string &package, string *content){    int pos = package.find(protocal_sep);    if (pos == string::npos)        return false;     string len_str = package.substr(0, pos);    int len = stoi(len_str);     int to_len = len_str.size() + 2 + len;    if (package.size() < to_len)        return false;     *content = package.substr(pos + 1, len); // 位置 加 长度     package.erase(0, to_len); // 解析的这一段,从缓冲区里去掉    return true;} class Request{public:    Request(int x, int y, char op) : x_(x), y_(y), op_(op)    {    }     Request()    {    }     ~Request()    {    } public:    bool Serialize(string *content) // 序列化为 字节流    {#ifdef Myself        string s = to_string(x_);        s += blank_sep;        s += op_;        s += blank_sep;        s += to_string(y_);         *content = s;        return true;#else        Json::Value root;         root["x"] = x_;        root["y"] = y_;        root["op"] = op_;         Json::FastWriter w;        *content = w.write(root);        return true;#endif    }     bool Deserialize(string &in) // 反序列化 将字节流解析    {#ifdef Myself        int l = in.find(blank_sep);        if (l == string::npos)            return false;        string x = in.substr(0, l); // 左闭,右开         int r = in.rfind(blank_sep);        if (r == string::npos)            return false;        string y = in.substr(r + 1);         if (l + 2 != r)            return false;         x_ = stoi(x);        y_ = stoi(y);        op_ = in[l + 1];        return true;#else        Json::Reader R;        Json::Value root;         bool ret = R.parse(in.c_str(), root);        if (!ret)            return false;        x_ = root["x"].asInt();        y_ = root["y"].asInt();        op_ = root["op"].asInt();        return true; #endif    }     void Printf()    {        cout << "任务请求" << x_ << op_ << y_ << "=?" << endl;    } public:    int x_;    int y_;    char op_;}; class Respone{public:    Respone(int res, int c) : result_(res), code_(c)    {    }     Respone()    {    }    ~Respone()    {    } public:    bool Serialize(string *out) // 序列化 形成字节流    {#ifdef Myself        string s = to_string(result_);        s += blank_sep;        s += to_string(code_);        *out = s;        return true;#else        Json::Value root;         root["result"] = result_;        root["code"] = code_;         Json::FastWriter w;        *out = w.write(root);        return true;#endif    }     bool Deserialize(string &in) // 反序列化 字节流解析    {#ifdef Myself        int pos = in.find(blank_sep);        if (pos == string::npos)            return false;        string x = in.substr(0, pos);        int result = stoi(x);         string y = in.substr(pos + 1);        int code = stoi(y);         result_ = result;        code_ = code;        return true;#else        Json::Reader R;        Json::Value root;         bool ret = R.parse(in.c_str(), root);        if (!ret)            return false;        result_=root["result"].asInt();        code_=root["code"].asInt();        return true;#endif    }     void Printf()    {        cout << "result " << result_ << "  code " << code_ << endl;    } public:    int result_;    int code_;};4.Tcpserver.hpp#pragma once#include <functional>#include <signal.h>#include "log.hpp"#include "Socket.hpp"#include "ServerCal.hpp"#include "Protocal.hpp" using func_t=function<string(string&)>;extern Log lg; class Server{public:    Server(uint16_t port,func_t calback):port_(port) ,calback_(calback)    {}     bool Init()    {        listensock_.Socket();        listensock_.Bind(port_);        listensock_.Listen();        lg("info","init server .... done");        return true;    }     void Start()    {        signal(SIGCHLD, SIG_IGN);        signal(SIGPIPE, SIG_IGN);        while(true)        {            string clientip;            uint16_t clientport;             int sockfd=listensock_.Accpect(&clientip,&clientport);            if(sockfd<0)            {                lg("fatal","accpect fail");                break;            }                        lg("info","accpect success");            if(fork()==0)            {                listensock_.Close();                string inbuffer_stream;                while(true)                {                    char buffer[1024];                    int n=read(sockfd,buffer,sizeof(buffer));                    if(n>0)                    {                        buffer[n]=0;                        inbuffer_stream+=buffer;                        lg("Debug", "debug:\n%s", inbuffer_stream.c_str());                         while(true)                        {                            string ret=calback_(inbuffer_stream);                            if(ret.empty())                            break;                             lg("Dedug","debug:\n%s",ret.c_str());                            lg("Dedug","debug:\n%s",inbuffer_stream.c_str());                             write(sockfd,ret.c_str(),ret.size());                        }                    }                    else                     {                        cout<<"错误了"<<endl;                        break;                    }                }                exit(0);            }            close(sockfd);        }    } private:    Sock listensock_;    uint16_t port_;    func_t calback_;};5.Tcpserver.cpp#include "Tcpserver.hpp"#include "ServerCal.hpp"  void Usage(string prco){    cout<<prco<<" [1024++]"<<endl;} int main(int argc,char*argv[]){    if(argc!=2)    {        Usage(argv[0]);        exit(0);    }     uint16_t port=stoi(argv[1]);    ServerCal calculator;     Server* tsrv=new Server(port,bind(&ServerCal::Calculator,&calculator,placeholders::_1));//第一个是this指针    //不用bind的话可以创建一个ServerCal的对象。    tsrv->Init();    tsrv->Start();    return 0;}的bind 是 C++11 引入的一个函数模板,用于创建一个新的可调用对象,将函数的某些参数“绑定”到具体值,然后返回一个可以稍后调用的函数。基本用法:std::bind(function, arg1, arg2, ..., argN);function:要绑定的函数。arg1, arg2, ...:绑定的参数,可以是具体值或占位符(std::placeholders)。#include <iostream>#include <functional> int add(int x, int y) {    return x + y;} int main() {    // 绑定第一个参数为 5,第二个参数用占位符    auto add5 = std::bind(add, 5, std::placeholders::_1);     std::cout << add5(3) << std::endl;  // 输出 8 (5 + 3)    return 0;}简化函数调用:std::bind 可以将函数和参数绑定,生成新函数,便于后续调用。灵活性:支持部分绑定函数参数,允许在调用时提供剩余的参数。与 lambda 比较:// 使用 std::bindauto add5 = std::bind(add, 5, std::placeholders::_1); // 使用 lambdaauto add5 = [](int x) { return add(5, x); };————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2401_83603768/article/details/151154240
  • [技术干货] Linux网络:网络基础-转载
    1. 协议分层问题一:为什么一般的软件要分层?1.层与层之间的耦合度是降低的,更重要的是一但分层,就相当于一个非常大的项目被肢解成了多个子项目,这样的话,在后期的维护上成本会非常低,所以第一点就是为了方便维护2.凡是划分到同一层的内部,其中的代码和逻辑必须是强相关的—高内聚(层内部),低耦合(层与层之间)3.问题是层状的!!!为了解决相应的问题,所以设计的时候也是按分层来设计的 在现实生活中,我们打电话给别人,看似是人与人之间直接进行交流,但是在实际上,是我们载荷电话沟通,然后电话将信息传输到另一台电话上,然后才让别人给听到,但是在逻辑上,人与人是在同一层的,所以我们会认为是人与人在沟通,同层内部高内聚,层与层之间低耦合。这样的好处就在于,某一层出问题,只需要处理某一层的问题就行了,其它层不用变,即使某一层的协议发生了变化,比如,如图的第一层的语言变成了英语协议,但是其下面的电话协议根本不用变,也无法被影响,依然能够正常交流,同理如果其电话机协议变成了无线电协议,其上层的汉语协议也无需变化,只需要处理无线电这一层的协议就行!!!1.1 OSI七层模型OSI(Open System Interconnection,开放系统互连)七层网络模型称为开放式系统互联参考模型,是一个逻辑上的定义和规范;把网络从逻辑上分为了7层. 每一层都有相关、相对应的物理设备,比如路由器,交换机;OSI 七层模型是一种框架性的设计方法,其最主要的功能使就是帮助不同类型的主机实现数据传输;它的最大优点是将服务、接口和协议这三个概念明确地区分开来,概念清楚,理论也比较完整. 通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯;但是, 它既复杂又不实用; 所以我们按照TCP/IP四层模型来讲解.1.2 TCP/IP五层(或四层)模型TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇.TCP/IP通讯协议采用了5层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求.物理层: 负责光/电信号的传递方式. 比如现在以太网通用的网线(双绞 线)、早期以太网采用的的同轴电缆(现在主要用于有线电视)、光纤, 现在的wifi无线网使用电磁波等都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗干扰性等. 集线器(Hub)工作在物理层.数据链路层: 负责设备之间的数据帧的传送和识别. 例如网卡设备的驱动、帧同步(就是说从网线上检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作. 有以太网、令牌环网, 无线LAN等标准. 交换机(Switch)工作在数据链路层.网络层: 负责地址管理和路由选择. 例如在IP协议中, 通过IP地址来标识一台主机, 并通过路由表的方式规划出两台主机之间的数据传输的线路(路由). 路由器(Router)工作在网路层.传输层: 负责两台主机之间的数据传输. 如传输控制协议 (TCP), 能够确保数据可靠的从源主机发送到目标主机.应用层: 负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等. 我们的网络编程主要就是针对应用层2. 操作系统与网络技术栈问题一:网络协议栈和我们之前学的操作系统有什么关系?数据链路层是软件,是在各种软件内部驱动实现的,其中的传输层和网络层是要在Linux内核中实现的在我们日常的生活中,我们也会刷视频来获取信息,这些信息是怎么来的?就是通过物理层中的网卡获取的!!!所以网络通信的本质也是在访问硬件!!!如果用户想要获取到视频资源,就需要去访问硬件,但是用户并没有资格去直接访问硬件,因为操作系统是硬件的管理者,操作系统不允许用户来访问底层硬件,操作系统并不信任用户,所以用户想要访问到硬件资源就必须要贯穿操作系统!!!用户层、传输层、网络层、数据链路层和物理层,从上往下,依次贯穿!!!网络通信的本质:就是贯穿协议栈的过程此时问题又来了,如果用户层要访问硬件资源,不能直接访问,要依次贯穿网络技术栈,但是操作系统可能让用户去访问、去贯穿网络技术栈吗?这显然不可能,那用户又是如何获取到硬件资源的呢?— ==系统调用接口!!!==通过系统调用接口,用户就可以获取到硬件资源,既然用户无法直接获取,但能让操作系统去获取,并给用户层提供一个接口,让用户层能拿到硬件资源。所以就有了利用系统调用来写出各种协议来供用户层使用!!! 大部分操作系统的内容和系统调用接口都是不一样的,但是网络协议栈是一样的!!!这是因为想要用使用网络,就必须按照网络制定的协议栈,又因为网络通信是要贯穿网络协议栈的(向上贯穿或者向下贯穿),主机1有用户层,主机2也一定会有用户层,同理任意两台主机的网络协议栈一定是是一样的!!!所以同层协议在逻辑上,就是在和对方的同层直接沟通!!!3.网络传输基本流程网络协议栈的层状结构中,由于每一层都有协议,所以每一层都要添加报头(报文 == 报头 + 有效载荷),才会将信息送到下一层。数据的自顶向下的封装的过程:1. 应用层定的协议,要在发送的信息前加一个报头(里面会有版本信息…),消息往下传递给传输层。(将要传输的内容叫做有效载荷)2. 传输层定的协议,再加入一个报头(里面会有信息的序号、源端口号和目的端口号…),消息往下传递给网络层。3. 网络层定的协议,再加入一个报头(里面会有该信息是谁发的(主机),要发生到哪里(主机)…),消息再往下传递给链路层。4. 链路层定的协议,再加入一个报头(里面的信息是从哪里发的(网卡),发送给谁的(网卡)…) 接着,报文就会交给网卡,网卡就会通过以太网,将报文发送给对方的主机的网卡上问题一:为什么远端机器拿到这个数据(报文)的时候一定是网卡先拿到数据?那是因为计算机里面只有一个网络通信的设备就叫做网卡!网卡拿到这个数据(报文)后,肯定要将数据交给操作系统,从硬件外设交到内存里。问题二:为什么网卡拿到数据后一定要将数据(报文)从硬件设备交到内存里?因为这是冯诺依曼体系规定!冯诺依曼体系规定网卡中的数据一定要从硬件设备交给内存里面当对方拿到报头后,是有办法区分报头和有效载荷的,这时候,只需要将报头取出来,就可以将剩下的有效载荷送给上层,同理,由于同层协议既然能给有效载荷添加报头,所以也能获取报头,每一层拿到报文后,都会将报头和有效载荷分离开来,层层传递,直到到了用户层,将信息传递成功。通信的过程:本质就是在不断的封装和解包的过程!除了上述的知识,不同层其实不止一种协议,从上往下其实可以看成是一个倒着的多叉树结构,每一层的协议都要考虑到底是传输给上层的哪种协议封装和解包中的问题(大部分协议的共性,未来学习具体协议的时候,都是这两个问题!!!)1. 几乎任何层的协议,都要提供一种能力,将报头和有效载荷分离的能力2. 几乎任何层的协议,都要在报头中提供,决定将自己的有效载荷交付给上层的哪一个协议只有知道了两个问题,再面对封装和解包的问题就不会困惑,因为这就是大部分协议的共性总结:本文浅谈了OSI七层网络模型和TCP/IP网络模型,主要对操作系统与网络技术栈的关系进行的探讨,并且对网络传输的基本流程进行了深刻的讲解————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/zby2791830200/article/details/151764387
  • [技术干货] 【合集】技术干货汇总
     Redis、MySQL、Linux、centos相关技术干货汇总:1、 Redis大量数据插入过程 —转载cid:link_02、 MySQL中EXPLAIN命令的使用场景及作用解读 —转载cid:link_23、MySQL中CHAR和VARCHAR类型的区别及说明 —转载cid:link_34、 MySQL EXPLAIN中key_len使用的终极指南 —转载cid:link_15、 Linux权限管理与ACL访问控制详解 —转载cid:link_46、在Linux系统上连接GitHub的方法步骤(适用2025年) —转载cid:link_57、 linux定时top、netstat输出到文件方式 —转载cid:link_68、 在Linux中配置和使用CAN通信的详细指南 —转载cid:link_79、 centos编译安装mariadb的详细过程 —转载https://bbs.huaweicloud.com/forum/thread-0237192349738667016-1-1.html10、、PostgreSQL扩展UUID-OSSP的使用方法 —转载cid:link_8
  • [技术干货] 在Linux中配置和使用CAN通信的详细指南 —转载
    1. 环境配置1.1 安装和配置必备工具在 Linux 系统上使用 CAN 通信,首先需要安装一些必备的工具和库:SocketCAN 驱动程序:这是 Linux 内核中实现 CAN 协议栈的模块,通常在大多数 Linux 发行版中已经默认启用。can-utils 工具:一个用于测试和调试 CAN 总线通信的工具集。编译器和开发工具:用于编译 C++ 代码的工具。安装依赖首先,确保你安装了所需的开发工具和库:1234sudo apt updatesudo apt install build-essentialsudo apt install can-utils  # 安装 can-utils 工具包sudo apt install libsocketcan-dev  # 如果需要安装 SocketCAN 开发库can-utils 包含多个实用工具,例如 cansend 和 candump,可以用于测试 CAN 总线的发送和接收。1.2 配置虚拟 CAN 接口(没有外设的情况)如果你没有物理 CAN 接口设备(如 USB-to-CAN 适配器),你可以使用虚拟 CAN 接口 vcan0 来进行测试。虚拟接口适用于不需要实际硬件的 CAN 总线仿真和开发。启用虚拟 CAN 接口加载 vcan 驱动模块:1sudo modprobe vcan创建虚拟 CAN 接口 vcan0:12sudo ip link add dev vcan0 type vcansudo ip link set vcan0 up测试虚拟接口:使用 can-utils 工具测试虚拟 CAN 接口:发送一个 CAN 帧:1cansend vcan0 123#deadbeef查看接收到的 CAN 数据:1candump vcan0这样,你就可以在没有实际硬件的情况下仿真 CAN 总线通信,进行开发和测试。1.3 配置物理 CAN 接口(有外设的情况)如果你有物理 CAN 外设(如 USB-to-CAN 适配器),你需要配置物理接口。检查 CAN 适配器:首先,检查系统是否识别到了 CAN 适配器,运行以下命令:1ip link show你应该看到类似 can0 或 can1 的接口。如果没有,请插入设备并确认驱动已加载。启用物理 CAN 接口:假设你的物理接口为 can0,你可以通过以下命令启用接口,并设置传输速率(例如 500 kbps):1sudo ip link set can0 up type can bitrate 500000测试物理接口:同样,使用 can-utils 发送和接收数据:发送数据:1cansend can0 123#deadbeef查看数据:1candump can0现在,你已经成功配置了 CAN 环境,无论是通过虚拟接口进行仿真,还是通过物理接口进行实际通信。2. 测试案例2.1 使用 can-utils 工具测试can-utils 提供了一些常用的命令行工具,可以快速地测试 CAN 总线的发送和接收。cansend:用于向 CAN 总线发送数据。发送一个数据帧:1cansend vcan0 123#deadbeef这会向 vcan0 接口发送一个带有 ID 为 0x123,数据为 deadbeef 的 CAN 帧。candump:用于查看 CAN 总线上的数据。查看所有 CAN 总线接口的数据:1candump vcan0你将看到类似下面的输出,显示收到的数据帧:1vcan0 123 [4] deadcanplayer:用于回放保存的 CAN 数据文件。回放一个 CAN 数据文件:1canplayer -I can_logfile.log这个工具在处理实际的 CAN 数据日志时非常有用。2.2 使用代码测试代码测试:发送和接收 CAN 数据我们将编写一个简单的代码示例,用于发送和接收 CAN 帧。创建线程池:我们将使用线程池来处理高并发的 CAN 数据接收。CAN 通信类:负责与 CAN 总线进行交互。main 函数:启动接收线程并发送数据。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182#include <iostream>              // 包含输入输出流,用于打印日志或调试信息#include <string>                // 包含 string 类的定义,用于字符串操作#include <cstring>               // 包含 C 风格字符串操作函数的定义#include <unistd.h>              // 包含 POSIX 系统调用,例如 close, read, write#include <net/if.h>              // 包含网络接口相关的定义#include <sys/ioctl.h>           // 包含 I/O 控制相关的函数定义,例如 SIOCGIFINDEX#include <fcntl.h>               // 包含文件控制相关的定义#include <linux/can.h>           // 包含 CAN 协议相关的定义#include <linux/can/raw.h>       // 包含原始 CAN 套接字定义#include <sys/socket.h>          // 包含 socket 套接字的相关定义#include <thread>                // 包含多线程支持的定义#include <atomic>                // 包含原子操作的定义,用于线程安全#include <mutex>                 // 包含互斥量的定义,用于线程同步#include <vector>                // 包含 vector 容器的定义#include <queue>                 // 包含队列容器的定义#include <functional>            // 包含函数对象的定义,用于队列任务#include <condition_variable>    // 包含条件变量定义,用于线程同步#include <chrono>                // 包含时间相关定义,用于控制线程等待时间#include <iostream>              // 包含 I/O 相关功能  // 线程池类,用于管理多个线程,执行异步任务class ThreadPool {public:    // 构造函数,初始化线程池,启动 numThreads 个线程    ThreadPool(size_t numThreads) : stop(false) {        // 创建并启动工作线程        for (size_t i = 0; i < numThreads; ++i) {            workers.push_back(std::thread([this]() { workerLoop(); }));        }    }      // 析构函数,停止线程池中的所有线程    ~ThreadPool() {        stop = true;             // 设置停止标志        condVar.notify_all();    // 通知所有线程退出        for (std::thread &worker : workers) {            worker.join();        // 等待所有线程结束        }    }      // 向线程池队列中添加一个任务    void enqueue(std::function<void()> task) {        {            std::lock_guard<std::mutex> lock(queueMutex);  // 锁住队列,避免多线程访问冲突            tasks.push(task);  // 将任务放入队列        }        condVar.notify_one();  // 唤醒一个等待的线程    }  private:    // 线程池中的工作线程函数    void workerLoop() {        while (!stop) {  // 当 stop 为 false 时,线程继续工作            std::function<void()> task;  // 定义一个任务对象            {                // 锁住队列,线程安全地访问任务队列                std::unique_lock<std::mutex> lock(queueMutex);                condVar.wait(lock, [this]() { return stop || !tasks.empty(); });  // 等待任务或停止信号                  // 如果 stop 为 true 且队列为空,退出循环                if (stop && tasks.empty()) {                    return;                }                  task = tasks.front();  // 获取队列中的第一个任务                tasks.pop();  // 从队列中移除该任务            }              task();  // 执行任务        }    }      std::vector<std::thread> workers;           // 线程池中的所有线程    std::queue<std::function<void()>> tasks;    // 任务队列,存储待处理的任务    std::mutex queueMutex;                      // 互斥锁,用于保护任务队列    std::condition_variable condVar;            // 条件变量,用于通知线程执行任务    std::atomic<bool> stop;                     // 原子变量,用于控制线程池的停止};  // CAN 通信类,用于发送和接收 CAN 消息class CanCommunication {public:    // 构造函数,初始化 CAN 通信    CanCommunication(const std::string &interfaceName) : stopReceiving(false) {        sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);  // 创建原始 CAN 套接字        if (sock < 0) {  // 如果套接字创建失败,输出错误并退出            perror("Error while opening socket");            exit(EXIT_FAILURE);        }          struct ifreq ifr;  // 网络接口请求结构体        strncpy(ifr.ifr_name, interfaceName.c_str(), sizeof(ifr.ifr_name) - 1);  // 设置接口名        ioctl(sock, SIOCGIFINDEX, &ifr);  // 获取网络接口的索引          struct sockaddr_can addr;  // CAN 地址结构体        addr.can_family = AF_CAN;  // 设置地址族为 CAN        addr.can_ifindex = ifr.ifr_ifindex;  // 设置接口索引          if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {  // 绑定套接字到指定的 CAN 接口            perror("Error while binding socket");            exit(EXIT_FAILURE);        }    }      // 析构函数,关闭 CAN 套接字    ~CanCommunication() {        if (sock >= 0) {            close(sock);  // 关闭套接字        }    }      // 发送 CAN 消息    void sendCanMessage(const can_frame &frame) {        if (write(sock, &frame, sizeof(frame)) != sizeof(frame)) {  // 写入套接字发送数据            perror("Error while sending CAN message");        }    }      // 接收 CAN 消息    void receiveCanMessages(ThreadPool &threadPool) {        while (!stopReceiving) {  // 如果没有接收停止信号,继续接收数据            can_frame frame;  // 定义一个 CAN 帧            int nbytes = read(sock, &frame, sizeof(frame));  // 从套接字中读取数据            if (nbytes < 0) {  // 如果读取失败,输出错误信息                perror("Error while receiving CAN message");                continue;            }              // 将解析任务提交到线程池            threadPool.enqueue([this, frame]() {                this->parseCanMessage(frame);  // 解析 CAN 消息            });        }    }      // 停止接收数据    void stopReceivingData() {        stopReceiving = true;  // 设置停止接收标志    }  private:    int sock;  // 套接字描述符    std::atomic<bool> stopReceiving;  // 原子标志,表示是否停止接收数据    std::mutex parseMutex;  // 解析数据时的互斥锁      // 解析 CAN 消息    void parseCanMessage(const can_frame &frame) {        std::lock_guard<std::mutex> lock(parseMutex);  // 锁住互斥量,确保解析数据时的线程安全        std::cout << "Received CAN ID: " << frame.can_id << std::endl;  // 打印 CAN ID        std::cout << "Data: ";        for (int i = 0; i < frame.can_dlc; ++i) {  // 遍历 CAN 数据字节            std::cout << std::hex << (int)frame.data[i] << " ";  // 打印每个字节的十六进制表示        }        std::cout << std::endl;    }};  // 主函数int main() {    ThreadPool threadPool(4);  // 创建一个有 4 个线程的线程池    CanCommunication canComm("vcan0");  // 创建一个 CanCommunication 对象,使用虚拟 CAN 接口 "vcan0"         // 启动一个线程来接收 CAN 消息    std::thread receiverThread(&CanCommunication::receiveCanMessages, &canComm, std::ref(threadPool));      // 创建并发送一个 CAN 消息    can_frame sendFrame;    sendFrame.can_id = 0x123;  // 设置 CAN ID 为 0x123    sendFrame.can_dlc = 8;  // 设置数据长度为 8 字节    for (int i = 0; i < 8; ++i) {        sendFrame.data[i] = i;  // 填充数据    }      canComm.sendCanMessage(sendFrame);  // 发送 CAN 消息      std::this_thread::sleep_for(std::chrono::seconds(5));  // 等待 5 秒,以便接收和处理消息         canComm.stopReceivingData();  // 停止接收数据    receiverThread.join();  // 等待接收线程结束      return 0;  // 程序正常退出}代码注释总结:线程池 (ThreadPool):提供了一个用于并发执行任务的线程池,通过 enqueue 函数将任务放入队列,工作线程从队列中取出任务执行。使用 std::mutex 保护任务队列的访问,并使用 std::condition_variable 实现线程间的同步。CAN 通信 (CanCommunication):提供了通过套接字进行 CAN 消息的发送与接收功能。使用 socket 创建原始 CAN 套接字,bind 绑定到指定的网络接口。发送和接收消息时,通过多线程处理接收到的数据,以提高并发性能。主程序 (main):创建线程池和 CAN 通信对象。启动接收线程并发送测试消息。主线程等待 5 秒以确保接收到的 CAN 消息被处理。这种方式可以在 Linux 系统中使用 C++ 进行高效的 CAN 通信,实现消息的发送与接收,并且利用线程池提高并发性能。
  • [技术干货] linux定时top、netstat输出到文件方式 —转载
    正确的终端命令12top -n 1 -b > ~/Desktop/top.txtnetstat -antlp脚本调用终端命令123456789101112131415161718import subprocessimport datetimep = subprocess.Popen(    'top -n 1 -b > ~/Desktop/top/{}.txt'.format(datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')),    shell=True,    stdout=subprocess.PIPE,    stderr=subprocess.STDOUT)p = subprocess.Popen(    '''netstat -antlp''',    shell=True,    stdout=subprocess.PIPE,    stderr=subprocess.STDOUT)# print(p.stdout.readlines()) # 输出到文件时,不要print,否则,文件里面就没有了with open('/home/liang/Desktop/netstat/{}.txt'.format(datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')), 'wb') as f:    f.writelines(p.stdout.readlines())crontab定时执行脚本crontab -e 打开 crontab 的编辑页面i进入编辑模式最下面加上1*/1 * * * * /home/liang/miniconda3/envs/draw3.9/bin/python /home/liang/PycharmProjects/my_script/a7.py
  • [技术干货] 在Linux系统上连接GitHub的方法步骤(适用2025年) —转载
    步骤一:检查并安装 Git首先,确保你的系统已安装 Git:1git --version如果未安装,使用下面的命令安装 (Debian/Ubuntu 系统):12sudo apt updatesudo apt install git步骤二:生成 SSH 密钥生成 SSH 密钥对,用于与 GitHub 进行安全通信:1ssh-keygen -t ed25519 -C "your_email@example.com"-t ed25519:指定密钥类型-C:注释信息,通常填入 GitHub 注册邮箱按回车使用默认路径(~/.ssh/id_ed25519)(这里需要注意一下路径,这个路径会因为个体机器不同而变化。)步骤三:将 SSH 公钥添加到 GitHub查看并复制公钥内容:1cat ~/.ssh/id_ed25519.pub登录 GitHub ,进入 SSH and GPG keys 页面点击“New SSH key”填写 Title:My Linux Laptop ;Key 填写复制的公钥点击“Add SSH key” 完成步骤四:测试 SSH 连接1ssh -T git@github.com第一次连接时,系统会提示确认,输入 yes,如果成功,会显示:1Hi username! You've successfully authenticated, but GitHub does not provide shell access.步骤五:克隆仓库或添加远程仓库克隆仓库:1git clone git@github.com:username/repository.git或者添加远程仓库:1git remote add origin git@github.com:username/repository.git将 username 和 repository 替换为你的 GitHub 用户名和项目名常见问题解答Q1:如果我已经有了 SSH 密钥,还需要重新生成吗?A1: 如果已有密钥,且未被其他服务使用,可直接使用:1cat ~/.ssh/id_ed25519.pubQ2:如何在多台设备上使用同一 GitHub 账户?A2: 每台设备单独生成密钥,将公钥添加到 GitHub ,GitHub 支持多个 SSH 公钥Q3:如何删除不再使用的 SSH 密钥?A3: 登录 GitHub,进入 SSH and GPG keys ,点击“Delete” 删除相应密钥
  • [技术干货] Linux权限管理与ACL访问控制详解 —转载
    一、基本权限概述Linux 系统通过权限控制用户对文件和目录的访问,核心权限分为读(r)、写(w)、执行(x),分别对应数字权限值 4、2、1。权限针对三类对象设置:所有者(u)、所属组(g)、其他用户(o),三者共同构成完整的权限控制体系。1. 基本权限与数字对应关系权限字符权限含义数字值组合示例(文件 / 目录)数字表示r读权限4只读(r--r--r--)444w写权限2读写(rw-rw-rw-)666x执行权限1读写执行(rwxrwxrwx)777-无权限0所有者读写执行,其他无(rwx------)700注意:目录必须拥有 x(执行)权限,否则无法通过 cd 命令切换到该目录。二、权限管理命令(chmod)chmod 用于修改文件或目录的权限,支持字符模式和数字模式两种操作方式。1. 字符模式语法1chmod [对象][操作][权限] 文件名/目录名对象:u(所有者)、g(所属组)、o(其他用户)、a(所有用户,默认)操作:+(添加权限)、-(移除权限)、=(设置权限,覆盖原有)权限:r、w、x示例:1234567891011# 给文件所有者添加执行权限 chmod u+x file.txt # 移除所属组的写权限 chmod g-w file.txt # 给所有用户设置读写权限(覆盖原有) chmod a=rw dir/2. 数字模式语法1chmod [所有者权限][所属组权限][其他用户权限] 文件名/目录名通过三位数字分别指定所有者、所属组、其他用户的权限(每位数字为 r/w/x 的数值和)。示例:1234567# 所有者读写执行,所属组读执行,其他用户只读(754) chmod 754 script.sh # 所有用户读写执行(777,谨慎使用) chmod 777 data/三、特殊权限特殊权限在基本权限基础上扩展了额外功能,适用于特定场景(如权限继承、身份临时切换)。1. SUID(Set User ID)标识:u+s(数字表示 4xxx)作用:应用于二进制命令文件,执行该命令时临时获得文件所有者的身份(通常为 root)。典型用途:允许普通用户执行需要 root 权限的命令(如 passwd 修改密码)。示例:123# 给 mkdir 命令添加 SUID 权限 chmod u+s /usr/bin/mkdir2. SGID(Set Group ID)标识:g+s(数字表示 2xxx)作用:应用于命令文件:执行时临时获得文件所属组的身份。应用于目录:目录中新建的文件 / 目录会继承该目录的所属组(而非创建者的基本组)。示例:123# 给目录添加 SGID 权限(新文件继承目录所属组) chmod g+s /tmp/shared_dir3. Sticky Bit(粘滞位)标识:o+t(数字表示 1xxx)作用:仅应用于目录,限制删除权限 ——只有文件所有者或 root 可删除该目录下的文件,其他用户即使有写权限也无法删除他人文件。典型场景:系统临时目录 /tmp 默认设置该权限。示例:123# 给共享目录添加粘滞位 chmod o+t /data/public四、默认权限与 umask新建文件或目录的默认权限由 umask(权限掩码)决定,umask 用于从最高权限中 “减去” 不需要的权限。1. 最高权限基准目录:最高权限为 0777(rwxrwxrwx)文件:最高权限为 0666(rw-rw-rw-,默认无执行权限)2. 默认 umask 值root 用户:0022(目录默认权限:0777-0022=0755;文件默认权限:0666-0022=0644)普通用户:0002(目录默认权限:0777-0002=0775;文件默认权限:0666-0002=0664)3. 临时修改 umask123# 临时将 umask 改为 0002(仅当前会话有效) umask 0002注意:不建议永久修改 umask,可能导致新建文件 / 目录权限过松,存在安全风险。五、所有者与所属组管理1. 修改所有者(chown)用于变更文件或目录的所有者,语法:1234567891011# 修改所有者 chown 新所有者 文件名/目录名 # 同时修改所有者和所属组(用 : 分隔) chown 新所有者:新所属组 文件名/目录名 # 递归修改目录(包括子文件/子目录) chown -R 新所有者:新所属组 目录名示例:1234567# 将 file.txt 的所有者改为 admin chown admin file.txt # 递归将 data/ 目录的所有者改为 root,所属组改为 dev chown -R root:dev data/2. 修改所属组(chgrp)专门用于变更文件或目录的所属组,语法:1234567# 修改所属组 chgrp 新所属组 文件名/目录名 # 递归修改目录所属组 chgrp -R 新所属组 目录名示例:123# 将 dir/ 的所属组改为 test chgrp test dir/六、ACL 访问控制(setfacl)ACL(Access Control List)提供更精细的权限控制,允许为特定用户或组单独设置权限,突破传统的 “所有者 - 所属组 - 其他” 三层权限限制。1. 基本语法(setfacl)1234567# 给用户设置权限 setfacl -m u:用户名:权限 文件/目录 # 给组设置权限 setfacl -m g:组名:权限 文件/目录权限:支持 r(读)、w(写)、x(执行),目录需 x 权限才能进入。2. 常用示例(1)给特定用户设置文件权限123# 允许 admin 用户对 file.txt 拥有读写权限 setfacl -m u:admin:rw file.txt(2)给特定组设置目录权限123# 允许 lisi 组对 dir/ 拥有读写执行权限 setfacl -m g:lisi:rwx dir/(3)设置目录权限继承(默认权限)为目录设置默认 ACL 后,新建的子文件 / 子目录会自动继承该权限:12345# 允许 admin 用户对 dir/ 有读写执行权限,并设置继承 setfacl -m u:admin:rwx dir/ # 目录本身权限 setfacl -m d:u:admin:rwx dir/ # 默认继承权限(d 表示 default)3. ACL 权限管理命令命令功能setfacl -b 文件名清除文件 / 目录的所有 ACL 权限setfacl -k 目录名清除目录的默认继承权限getfacl 文件名查看文件 / 目录的 ACL 权限详情总结基本权限:通过 chmod 管理 rwx 权限,支持字符和数字两种模式。特殊权限:SUID、SGID、Sticky Bit 适用于身份临时切换、权限继承等场景。默认权限:由 umask 决定,root 与普通用户默认值不同。所有者 / 组管理:chown 和 chgrp 用于变更文件的归属关系。ACL 权限:setfacl 提供精细化控制,支持为特定用户 / 组单独授权及权限继承。
  • [技术干货] Linux NAS盘挂载详解 —转载
    安装mount.cifs软件包或nfs.utils取决于你nas的格式12yum -y install cifs-utilsyum -y install nfs-utils使用mount 挂载1234567891011121314151617181920#需要先确定端口是否开启,最保险的办法是全部都开启#cifs开启445,如果启用NBT,那么就同时监听UDP 137、138端口和TCP139,445,最保险的办法是全开#nfs 开启2049,111,同时修改一下nfs的端口,方便防火墙做映射,下面涉及的端口最好也开一下/etc/sysconfig/nfs 文件  MOUNTD_PORT="4002"STATD_PORT="4003"LOCKD_TCPPORT="4004"LOCKD_UDPPORT="4004" #需要 rpcbind 和 nfs 服务(最好设置为开机自启)systemctl restart rpcbind.servicesystemctl restart nfs.service #有些版本无需vers=1.0 和 sec=ntlm也可以使用#cifs 使用这种方式mount -t cifs -o rw,iocharset=utf8,username=test,password=test,vers=1.0,port=445,sec=ntlm //nas_ip/data /mnt/nas # nfs 使用以下命令mount -t nfs -o rw,iocharset=utf8,username=test,password=test,sec=ntlm nas_ip:/data /mnt/nas#一般来说是秒执行,如果卡着不运行说明还是网络问题,可以排查一下nas的白名单策略和防火墙参数说明12345678910111213141516171819202122232425262728-t  挂载文件系统的类型-o <选项> 指定挂载文件系统时的选项,有些也可写到在 /etc/fstab 中。常用的有:   defaults 使用所有选项的默认值(auto、nouser、rw、suid)   username 用户名   password  密码   port 端口   vers 版本   sec  安全策略        none - 尝试以空用户身份连接(无名称)        krb5 - 使用 Kerberos 版本 5 身份验证        krb5i - 使用 Kerberos 身份验证并强制启用数据包签名        ntlm - 使用 NTLM 密码散列        ntlmi - 使用 NTLM 密码散列和强制数据包签名        ntlmv2 - 使用 NTLMv2 密码散列        ntlmv2i - 使用 NTLMv2 密码散列和强制数据包签名        ntlmssp - 使用封装在原始 NTLMSSP 消息中的 NTLMv2 密码散列        ntlmsspi - 使用封装在 Raw NTLMSSP 消息中的 NTLMv2 密码散列,并强制数据   auto/noauto 允许/不允许以 –a选项进行安装   dev/nodev 对/不对文件系统上的特殊设备进行解释   exec/noexec 允许/不允许执行二进制代码   suid/nosuid 确认/不确认suid和sgid位   user/nouser 允许/不允许一般用户挂载   codepage=XXX 代码页   iocharset=XXX 字符集   ro 以只读方式挂载   rw 以读写方式挂载   remount 重新安装已经安装了的文件系统   loop 挂载“回旋设备”以及“ISO镜像文件”问题总结123456789101112# 错误记录1mount: /mnt/nas: mount(2) system call failed: Operation not supported.缺少 cifs-utils 安装包# 错误记录二mount error(95): Operation not supportedkernel 问题,需要添加 vers=1.0# 错误记录三mount error(13): Permission deniedmount 的安全策略问题,添加 sec=ntlm 解决# 错误记录四mounting read-only没有添加权限,需要添加写入权限 rw
  • [技术干货] Linux 信号-转载
    一. 信号基本概念信号是一种用于进程间通信或系统通知进程发生特定事件的机制。它可以被视为操作系统向进程发送的“消息”,用于告知进程发生了某种异常等需要处理的事情。信号的生命周期分为三个阶段 信号产生 信号保存 信号处理。接下来我将按照这三个阶段进行一一讲解。首先我们来粗略的了解一下信号的基本概念。信号的产生方式有5种,可以由操作系统,其他进程,自身进程,硬件等不同的发送方。当信号产生之后,并不会立即作出处理,内核会先将其暂存,存入一张未决信号表当中,此时要区分对于该信号是否阻塞,待进程有空后再进行处理。当进程成功接收到信号后,此时有三种处理方式,忽略处理,默认动作,信号捕捉。  我们使用 kill -l 指令可以查看到所有的信号,我们把前三十一个称为普通信号,它们都是大写的宏,旁边的数字就是它们的对应值。 使用man 7 signal 可以查看到里边大多数信号的默认动作,以及使用的默认效果。 二. 信号的产生首先我们来认识一个函数 signalsignal:Linux 操作系统用于处理信号的一个系统调用。它允许用户指定一个函数,当接收到特定信号时进行函数调用。这样我们便可以对信号进行自定义处理。 signum:需要处理的信号类型,可以是宏定义的,也可以是对应的数字。handler:函数指针类型,用于接收需要的信号。参数要包含你一个 int 来接收信号1. 通过终端按键产生信号终端按键可以通过终端驱动程序触发特定信号,如我们熟知的 Ctrl + c ,下面我们介绍几个信号同时演示如何通过终端按键产生信号。1-1 操作演示Ctrl + c (SIGINT)会产生 2 号信号,默认会进行终止进程的处理,终止的是前台进程(死循环)。Ctrl + \ (SIGQUIT)会产生3号信号,除了终止进程,还会生成 core dump 文件,主要用于调试使用。9号信号 (SIGKILL)强制终止进程,该信号不可被捕捉,阻塞。19号信号(SIGSTOP)用于暂停进程执行信号,它相当于“冻结”进程而非终止,具有强制性。下面我们就以 2 号信号作为演示:#include <iostream>#include <unistd.h>#include <signal.h>using namespace std; void handler(int signal){    cout<<"我是信号:"<<signal<<endl;} int main(){    cout<<"我是进程:"<<getpid()<<endl;    signal(2,handler);    while(true)    {        cout<<"我是进程:"<<getpid()<<"我正在等待信号"<<endl;        sleep(2);    }    return 0;}AI写代码cpp运行上述代码,将2号信号进行捕捉后特殊处理,我们来看效果。  当我们Ctrl + c 时,进程不会终止,而是执行了我的函数,我们只有使用 9 号信号才能对其进行终止。同理,其他按键也与其类似。1-2 理解OS如何得知键盘数据键盘和其他外部设备都属于硬件,硬件向中断控制器中发送信号,发起中断,这也就是所谓的硬件中断,控制器为其分配一个中断号,确定了唯一标识源。紧接着通知CPU,CPU得知中断获取中断号。CPU 根据中断号,结合中断向量表,执行中断处理方法。 通过上面的过程,我们可以感受到,硬件中断行为和软件中断(信号处理)有着异曲同工之处。其实,信号就是从软件角度,模拟硬件中断的行为。只不过,硬件中断是发送给CPU,软件中断发送给进程。2. 调用系统命令向进程发送信号我们可以通过 kill -信号 进程id 的方法,来对进程发送信号。2-1 操作演示我们还是使用先前的代码#include <iostream>#include <unistd.h>#include <signal.h>using namespace std; void handler(int signal){    cout<<"我是信号:"<<signal<<endl;} int main(){    cout<<"我是进程:"<<getpid()<<endl;    signal(2,handler);    while(true)    {        cout<<"我是进程:"<<getpid()<<"我正在等待信号"<<endl;        sleep(2);    }    return 0;}AI写代码cpp运行我们启动进程,当我们使用 kill 对进程发送2号进程时,屏幕打印出信息,执行了函数。  3. 使用函数产生信号kill 命令是调用 kill 函数实现的,kill 函数可以给一个指定的进程发送指定的信号。 参数:pid:发送信号进程的pidsig:发送的信号返回值:成功返回0,失败返回-1raise:给当前进程发送指定的信号 参数:sig:发送的信号返回值:成功返回0,失败返回 non-zeroabort:使当前进程接收到信号而异常终止 3-1 操作演示首先我们写一个系统调用 kill 函数的程序#include <iostream>#include <unistd.h>#include <signal.h>#include <sys/types.h>using namespace std; int main(int argc,char* argv[]){    if(argc != 3)    {        cerr<<"Usage:"<<argv[0]<<"-signalnumber pid"<<endl;    }     int number = stoi(argv[1]+1);    pid_t id = stoi(argv[2]);     int n = kill(id,number);        return n;} 接着再来一个测试代码#include <iostream>#include <unistd.h>#include <signal.h>using namespace std; int main(){    while(true)    {        cout<<"我是进程:"<<getpid()<<endl;        sleep(3);    }    return 0;}我们来看一下运行结果 当我们调用2号信号后,测试用例终止。4. 软件条件产生信号由软件逻辑或用户行为触发的信号。下面我们来介绍一下一些软件条件下产生的信号。13号信号(SIGPIPE),用于处理管道中的写入错误,例如当读端进程关闭,写端进程仍向管道写入,就会触发 SIGPIPE 。14号信号(SIGALRM),用于通知进程的预设时间已到。我们通常用 alarm 函数来触发该信号。alarm:设置一个定时器,second秒后触发信号 参数:seconds:秒数返回值:若先前设置了定时器,返回剩余秒数,否则返回0。4-1 操作演示下面我们来测试一下闹钟我们来看下面的代码#include <iostream>#include <signal.h>#include <cstdlib>#include <unistd.h>#include <sys/types.h>using namespace std; void handler(int signum){    cout << "接收到信号:" << signum << endl;    exit(1);} int main(){    int cnt = 1;    alarm(1);    signal(14, handler);    while(true)    {        cout<<cnt<<endl;        cnt++;    }    return 0;}运行结果: 当闹钟响起时接收到14号信号,最终返回。4-2 理解闹钟在底层,alarm函数与内核定时器机制相关联,当alarm被调用之后,内核会生成一个软定时器。当定时器到期后,内核会发送SIGALRM信号。当进程中多出用到alarm时候,OS 会借助最小堆,判断要先向谁发送信号。5. 硬件异常产生信号硬件异常被硬件以某种方式通过硬件检测通知内核,内核向进程发送适当信号。例如当前进程执行了除0的指令,CPU 运算单元产生异常,内核会将这个异常解释为 SIGFPE 8号信号发送给进程。或者进程访问了野指针,MMU 会产生异常,会向进程发送 SIGSEGV 11号信号。关于野指针问题这个问题与 CPU 内部的MMU,CR2 ,CR3有关联。MMU和页表用来管理虚拟内存从虚拟地址到物理地址的转换。CR3用于切换不同进程的页表,CR2用于存储当前页表的错误虚拟地址。当MMU无法将虚拟地址与物理地址进行关联时,CR2会存储该虚拟地址,并产生异常信号,向当前进程发送 11号信号。6.term 与 coreterm 和 core 是信号默认动作的表示。1. term 是terminate 的缩写,表示默认终止进程。2. core 动作在终止进程的同时,还会生成一个core dump文件,这个文件用于调试。当进程退出时,core dump为0表示没有异常退出,如果是1表示异常退出。 综上所述,产生信号的方法有5种,但本质其实只有三种,硬件触发如键盘或硬件异常等,软件触发如闹钟管道,用户对系统调用kill命令等。所有的信号产生最终都是由 OS 进行执行,产生的信号不会立即作出处理,而是在合适的时候进行。所以说,这些暂时没有执行的信号就会进行保存,那么接下来我们就来理解一下信号的保存。三. 信号的保存操作系统用三张表来对信号进行管理。Block表(阻塞表):一个位图,用于表示哪些信号被阻塞,阻塞为1,未阻塞为0Pending表(未决表):未决即信号已经产生,但是暂时未被进程处理。当信号处于未决时,位图表为1。Handler表(处理函数表):一个函数指针数组,用于表示信号接收后的处理动作。可以是默认函数,忽略函数,自定义函数。有阻塞不一定有未决,但有未决一定是因为阻塞。进程对该信号进行阻塞,若用户一直不传递该信号,该信号就不会进入未决状态。若该信号进入了未决状态,说明该信号已经被阻塞了,正在等待进程处理。操作系统通过两张位图 + 一张函数指针表 ,就完成了让进程识别不同的信号。 1. 信号集 sigset_tsigset_t 是一种数据类型,用于接收阻塞和未决标志专门设置的数据类型。可以用来表示该数据的有效或者无效。2. 信号操作函数sigset_t 存储在内部,从使用者的角度我们不必关心,我们只需要能调用它的封装函数来对 sigset_t 进行操作。#include <signal.h> int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set, int signo);int sigdelset(sigset_t *set, int signo);int sigismember(const sigset_t *set, int signo);参数:set:指向信号集signo:某种信号返回值:成功返回0,失败返回-1sigemptyset:将指向的信号集所有信号对应的bit清空。sigfillset:将指向的信号集所有的信号bit置为1.sigaddset:向信号集某一个信号的bit置为1.sigdelset:向信号集某一个信号的bit置为0.sigimember:判断一个信号集中是否包含某种信号,包含返回1,不包含返回-1sigprocmask:读取或更改进程的信号屏蔽字#include <signal.h>int sigpromask(int how, const sigset_t *set, sigset_t *oset);参数:how:如何修改当前的信息掩码{SIG_BLOCK:将信号集中的所有信号进行阻塞SIG_UNBLOCK:将信号集中的所有信号解除阻塞SIG_SETMASK:将信号集更新为set,并且返回oldset}set:新的更改过的信号集old:更改前的信号集。返回值:成功返回0,失败返回-1.sigpending:读取当前进程的未决信号集,通过set传出#include <signal.h>int sigpending(sigset_t *set);AI写代码cpp运行参数:set:接收的信号集返回值:成功返回0,失败返回-13. 操作演示我们设计一个程序,对2号信号进行阻塞,然后不断打印未决表观察,在10次轮询后,解除对2号信号阻塞,再对未决表进行观察。#include <iostream>#include <signal.h>#include <unistd.h>#include <cstdio>#include <sys/types.h>#include <sys/wait.h>using namespace std; //先屏蔽2号信号,轮询10次解除屏蔽 void PrintPending(sigset_t &pending){    cout<<"["<<getpid()<<"]:";    for(int i = 31;i>=1;i--)    {        if(sigismember(&pending,i))        {            cout<<"1";        }        else        {            cout<<"0";        }    }    cout<<endl;} void headler(int signo)//打印信号表{    cout<<"接收到信号:"<<signo<<endl;    cout<<"----------------------"<<endl;    sigset_t pending;    sigpending(&pending);    PrintPending(pending);    cout<<"----------------------"<<endl;} int main(){    //初始化    sigset_t new_set,old_set;    signal(2,headler);    sigemptyset(&new_set);    sigemptyset(&old_set);    sigaddset(&new_set,2);        //设置阻塞表    sigprocmask(SIG_BLOCK,&new_set,&old_set);     int cnt = 10;     while(true)    {        sigset_t pending;        sigpending(&pending);        PrintPending(pending);         if(cnt == 0)        {            sigprocmask(SIG_SETMASK,&old_set,&new_set);            cout<<"解除对2号信号屏蔽"<<endl;        }        cnt--;        sleep(1);    }    return 0;}AI写代码cpp运行运行结果: 我们发现当我们发送2号信号时,未决表接收到信号,但是因为阻塞所以进程接收不到。当解除信号屏蔽后,先前发送的2号信号就立马被接受到了,此时再发送信号就不会出现阻塞。四. 信号的处理当我们进程接收到信号后,有三种处理方式1. 忽略信号:忽略该信号,不对其做任何处理2. 捕捉信号:进程可以注册一个对于该信号的一个特殊捕捉函数,当该信号触发时,进程接收信号并做出相应处理。3. 默认处理:若进程没有对该信号进行注册,系统会按照默认的方法进行处理。signal(2, handler);signal(2, SIG_IGN);signal(2, SIG_DFL);AI写代码cpp运行handler:自定义函数SIG_IGN:忽略信号SIG_DFL:默认处理我们从上文得知,信号并不会被立即处理,而是会先储存等待合适的时机再进行处理。那合适的时机是什么时候呢?先说结论,是从内核态切换为用户态的时候。简单讲,当我们执行自己的代码,访问自己的数据就是用户态;进入系统调用,以操作系统的身法运行就是内核态。当程序出现中断,异常或者系统调用的时候,我们会从用户态转为内核态。在内核态中以操作系统的身份完成工作后,将会从内核态切换回用户态,此时操作系统会对未决的信号进行检测处理(通过三张表判断),因为信号的三张表存储在PCB(进程控制块)中,需要调用三张表就必须处在内核态。若信号执行的是默认或者忽略,此时已经完成了工作。若信号执行的是自定义函数,此时返回用户态之后会处理信号函数,并且返回sys_sigreturn()使其进入内核态。最后在内核态和用户态切换的间隙处理函数,最后返回到主流程被中断的地方,这就完成了信号的捕捉流程。也就是说,如果信号执行的是自定义函数,那么就需要4次的用户态和内核态的切换 简化一下: 我们再来认识一个函数sigaction:自定义信号处理方式#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); struct sigaction {    void (*sa_handler)(int);  // 信号处理函数(类似 signal() 的回调)    sigset_t sa_mask;         // 信号处理期间临时阻塞的信号集    int sa_flags;             // 信号处理的标志位(如 SA_RESTART、SA_NODEFER 等)    void (*sa_sigaction)(int, siginfo_t *, void *);  // 高级处理函数(需配合 SA_SIGINFO 志)};AI写代码cpp运行参数:signum:信号数字act:结构体定义信号行为,传入的新的行为old:旧的行为返回值:成功返回0,失败返回-1.五. 操作系统是如何运行的1. 硬件中断当我们在键盘上输入命令或数据时,键盘的电路会检测按键的按下和释放,并产生对应的电信号。电信号随后转换为中断信号,通过硬件连线传递到CPU的中断控制器。中断控制器根据信号的优先级和当前CPU的状态,决定是否向CPU发送中断请求。CPU通过中断机制来响应中断请求。在保护下,CPU会维护一个中断描述符表,该表包含中断服务相应的地址信息。当中断发生后,会根据该表来找到对应执行方法的地址。一旦CPU接收到来自键盘的中断请求,就会暂停当前正在执行的程序,然后跳转到特定的中断处理服务地址,处理对应的方法。中断处理操作会执行必要的操作,如读取键盘状态,更新数据,发送响应等。处理完中断后,CPU会恢复之前保存的状态,并继续执行原来的程序。2. 时钟中断进程是在操作系统的指挥下被调度运行的,而操作系统是由时钟中断进行管理的。时钟中断源于硬件定时器,有计算机的主板芯片或处理器芯片提供,通过定时器计数器来实现中断功能。功能:1. 维护系统时间:每当时钟中断发生,内核系统会重新维护一个时间,通过更新系统保证时间的准确性,为用户提供可靠的时间信息。2. 任务调度:在多进程中,时钟会为每一进程分配时间片。当一个时间片用完了,内核就会重新选择下一个要运行的进程,并切换上下文,这样就可以保证公平的进行进程调度。3. 计录进程执行时间:每当进程或线程被抢占或者切换时,时钟会记录下抢占时间,方便我们后序了解系统的运行情况。3. 死循环操作系统在启动之后就会变成一个死循环,但这个循环是可控循环。会进行事件驱动,动态调度,资源管理等操作。通过时钟中断,可以为每一个进程进行时间分配,这样可以确保每个进程都有机会获得处理器资源。4. 软中断软中断是软件主动触发“中断”,从而进入内核态执行特权操作。也被称为“陷阱”,被用作内核态与用户态直接的通信桥梁。软硬中断的具有共性,中断之后需要进行状态切换(从用户态到内核态),上下文保存(保存用户态寄存器,计数器等信息),跳转执行(根据中断号,跳转处理),处理返回(执行完内核逻辑后返回)。在x86下32位机器通常用 int 0x80指令实现调用;在x86下64位机器通常用syscall指令执行调用。通过软中断,我们从用户态到了内核态,这样我们就可以实现系统调用。在内核态中,所有的系统调用其实都是被封装好的一个个函数指针数组,我们通过对应的下标,就可以对其进行访问。 这些被系统调用的系统函数会以数组下标的形式存储在 寄存器 中,通过下标找对应的系统调用表找到对应的处理函数并调用。执行完后再从内核态返回用户态,恢复上下文。这样就完成了系统调用。 紧急着,上层对于这些系统调用接口进行封装,这样我们用户就无法直接调用系统接口,保证了安全性。5. 缺页中断,内存碎片处理,除零野指针错误这些问题会被CPU内部转化为软中断,走中断处理例程进行相应处理。六.对内核虚拟地址空间的理解我们这里只对内核的虚拟地址空间进行讨论。内核虚拟地址空间存储在虚拟地址空间的高地址处,且每一份进程共享这一份内核页表,用于对操作系统进行管理。通常用户态在【0-3G】而内核区在【3-4G】处。CPU通过 int 0x80 或 syscall 指令进行用户态到内核态的切换,将当前状态记录在 CPL 中,其中0代表内核,3代表用户。在内核区中存储了内核页表,存储着内核处理中断异常硬件等各种方法的函数指针。  总结:信号是操作系统中一种轻量级的异步通信机制,通过预定义的信号编号传递事件通知,实现进程对异常、外部请求等事件的响应。其核心是 “事件触发 - 处理” 模型,兼具灵活性(自定义处理)和强制性(关键信号不可忽略),是进程间通信和系统异常处理的基础工具。 ————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2401_86449430/article/details/150430290
  • [技术干货] KingbaseES 快速安装指南:Windows、Docker 与 ISO 包三种方式详解 -转载
    中电科金仓(北京)科技股份有限公司(以下简称“电科金仓”)成立于1999年,是成立最早的拥有自主知识产权的国产数据库企业,也是中国电子科技集团(CETC)成员企业。电科金仓以“提供卓越的数据库产品助力企业级应用高质量发展”为使命,致力于“成为世界卓越的数据库产品与服务提供商”。    电科金仓自成立起始终坚持自主创新,专注数据库领域二十余载,具备出色的数据库产品研发及服务能力,核心产品金仓数据库管理系统KingbaseES(简称“KES”)是面向全行业、全客户关键应用的企业级大型通用数据库。KES产品V9版本已通过国家权威机构认证,产品核心源代码自主率达到100%。2018年,电科金仓申报的“数据库管理系统核心技术的创新与金仓数据库产业化”项目荣获国家科学技术进步二等奖。金仓数据库管理系统KES于2022年入选国务院国资委发布的十项国有企业数字技术典型成果,彰显数据库领域国家队硬实力。继2023年金仓数据库管理系统V8通过第一批《安全可靠测评》后,2024年金仓数据库管理系统V9、金仓分布式HTAP数据库软件集群V3再度入围,至此电科金仓共计2款产品3个版本通过《安全可靠测评》*。 🥇 点击进入金仓数据库专栏,本专栏聚焦金仓数据库(KingbaseES)这一国产企业级融合数据库,为开发者及技术决策者提供从基础操作到架构设计的系统化学习路径。从多语法兼容(Oracle/MySQL/PostgreSQL)、多模数据存储(关系 / 文档 / 时序 / GIS)等功能展开讲解!🌞 正文开始:KingbaseES是一款功能强大的数据库管理系统,支持多种硬件架构和安装方式。本文将详细介绍通过Docker和ISO包两种方式快速安装KingbaseES的步骤,帮助开发者快速部署和体验该数据库。Docker方式快速安装KingbaseES环境准备硬件环境要求KingbaseES Docker软件包支持通用X86_64、龙芯、飞腾、鲲鹏等国产CPU硬件体系架构,满足不同场景的硬件需求。软件环境要求软件包名称    版本说明Docker    20.10.0及以上版本安装步骤创建安装路径使用root用户在宿主机执行以下命令,创建数据库持久化存储路径:mkdir -p /opt/kingbasemkdir -p /opt/kingbase/datachmod -R 755 /opt/kingbase/data导入镜像首先获取KingbaseES的Docker镜像(如kingbase.tar),可通过电科金仓官网、销售人员、售后支持人员或代理商获取。进入软件安装包目录并查看容器镜像:cd /opt/kingbasels -al使用root用户将镜像导入至Docker镜像库:docker load -i /opt/kingbase/kingbase.tar若执行上述命令报错,可尝试使用docker import kingbase.tar命令重新导包。启动实例最小启动命令(未进行数据持久化):docker run -tid --privileged \-p 54321:54321 \--name kingbase  \kingbase:v1 /usr/sbin/init数据持久化启动命令:docker run -tid --privileged \-p 54321:54321 \--name kingbase  \-v /opt/kingbase/data:/home/kingbase/userdata \kingbase:v1 /usr/sbin/init其中,/home/kingbase/userdata是金仓数据库默认数据文件目录,/opt/kingbase/data是本机存储持久化目录,可根据实际需求修改。安装后检查执行docker ps命令,查看容器运行状态,若能看到创建的kingbase容器信息,则说明安装启动成功。命令行界面访问进入容器并执行bash:docker exec -it kingbase /bin/bash 执行ksql访问命令行界面:ksql -Ukingbase -d testISO包方式快速安装KingbaseES安装步骤概览ISO包安装主要包括:硬件环境检查、创建安装用户、创建安装目录、安装包准备、安装包挂载、安装并启动KingbaseES、安装后检查等步骤。详细步骤硬件环境要求支持架构:通用X86_64、龙芯、飞腾、鲲鹏等国产CPU硬件体系架构。具体要求(标准版/企业版/专业版/开发版):CPU:X86、龙芯、飞腾、鲲鹏内存:512MB以上硬盘:11GB以上空闲空间备注:/tmp目录需要至少10G空间。若安装过程中出现存储空间不足,需先释放足够磁盘空间;硬件配置不满足要求时,需更换硬件设备。创建安装用户使用root用户创建kingbase用户:useradd -u2000  kingbase或若需修改已有用户:kill -9 `lsof -u kingbase`usermod -u 2000 kingbasegroupmod -g 2000 kingbase设置kingbase用户密码(需输入两次且保持一致):passwd kingbase注意:创建安装用户后,后续操作默认使用kingbase用户进行。创建安装目录KingbaseES默认安装目录为/opt/Kingbase/ES/V9,使用root用户创建该目录并设置权限:mkdir -p /opt/Kingbase/ES/V9chmod o+rwx /opt/Kingbase/ES/V9安装包准备获取安装包:通过电科金仓官网、销售人员、售后支持人员或代理商获取对应平台的ISO安装程序。上传安装包至服务器存放路径(示例路径为/opt/software/),并使用root用户创建存放软件路径:mkdir -p /opt/software/ mkdir -p /opt/software/KingbaseESV9 安装包的挂载使用root用户挂载iso文件:cd /opt/software/mount KingbaseES_V009R001C010_Lin64_install.iso ./KingbaseESV9安装KingbaseES使用kingbase用户执行数据库安装命令:cd /opt/software/KingbaseESV9sh setup.sh -i console随后按照安装向导依次进行操作,包括选择安装类型(默认安装新实例)、阅读并接受许可协议、选择安装路径(默认/opt/Kingbase/ES/V9)、选择安装集(默认完全安装)等,直至安装完成。安装后操作安装完成后,可根据提示进行初始化数据库等操作,例如手动初始化数据库命令:/opt/Kingbase/ES/V9/KESRealPro/V009R001/Server/bin/initdb -U "system" -x "12345678ab" -E "UTF-8" --lc-ctype="zh_CN.UTF-8" --lc-collate="zh_CN.UTF-8" -D "/opt/Kingbase/ES/V9/data"AI写代码bash1通过以上两种方式,可快速完成KingbaseES的安装与部署,根据实际应用场景选择合适的安装方式即可。在安装过程中,若遇到问题可参考官方文档或联系技术支持。Windows下载安装下载数据库    金仓官网提供了金仓各产品各版本的安装包、补丁包、对应的工具、接口驱动、授权文件等下载服务。可以通过页面中的筛选栏选择所需产品及版本进行下载,我这里下载的是windows版本的x64完整版金仓数据库。下载地址:https://download.kingbase.com.cn/xzzx/index.htm    这里下载好的是iso镜像文件,直接双击打开,然后把内容复制出来。 安装配置    单击 KINGBASE.EXE 开始安装!    我这里选择默认的中文安装,点击 确定    单击 下一步    单击 下一步    我这里选择的完全安装,点击 下一步    我这里选择试用,点击 下一步    可以自定义安装路径,点击 下一步    点击 安装    默认数据文件就行,点击 下一步     填写相关配置信息,system的密码自定义,字符集编码,选择的是UTF8;数据库兼容模式选择的是MySql;是否大小写敏感选择的是NO,存储块大小选择的是8K,点击 完成    这里数据库就安装完成了,是不是相当简单。 联系博主    xcLeigh 博主,全栈领域优质创作者,博客专家,目前,活跃在CSDN、微信公众号、小红书、知乎、掘金、快手、思否、微博、51CTO、B站、腾讯云开发者社区、阿里云开发者社区等平台,全网拥有几十万的粉丝,全网统一IP为 xcLeigh。希望通过我的分享,让大家能在喜悦的情况下收获到有用的知识。主要分享编程、开发工具、算法、技术学习心得等内容。很多读者评价他的文章简洁易懂,尤其对于一些复杂的技术话题,他能通过通俗的语言来解释,帮助初学者更好地理解。博客通常也会涉及一些实践经验,项目分享以及解决实际开发中遇到的问题。如果你是开发领域的初学者,或者在学习一些新的编程语言或框架,关注他的文章对你有很大帮助。    亲爱的朋友,无论前路如何漫长与崎岖,都请怀揣梦想的火种,因为在生活的广袤星空中,总有一颗属于你的璀璨星辰在熠熠生辉,静候你抵达。     愿你在这纷繁世间,能时常收获微小而确定的幸福,如春日微风轻拂面庞,所有的疲惫与烦恼都能被温柔以待,内心永远充盈着安宁与慰藉。    至此,文章已至尾声,而您的故事仍在续写,不知您对文中所叙有何独特见解?期待您在心中与我对话,开启思想的新交流。     💞 关注博主 🌀 带你实现畅游前后端!     🥇 从零到一学习Python 🌀 带你玩转Python技术流!     🏆 人工智能学习合集 🌀 搭配实例教程与实战案例,帮你构建完整 AI 知识体系     💦 注:本文撰写于CSDN平台,作者:xcLeigh(所有权归作者所有) ,https://xcleigh.blog.csdn.net/,如果相关下载没有跳转,请查看这个地址,相关链接没有跳转,皆是抄袭本文,转载请备注本文原地址。————————————————                                                    原文链接:https://blog.csdn.net/weixin_43151418/article/details/150473138
  • [技术干货] 【Linux】环境变量-转载
    概念        环境变量:一般指的是在操作系统中用来指定操作系统运行环境的一些参数。        就比如说我们编写的C/C++代码在连接时从来不知道我们所连接的动静态库在哪里,但是照样可以连接成功。其原因就是有环境变量帮助编译器进行查找。        环境变量通常具有某些特殊用途,其在系统中还具有全局特性。命令行参数        我们知道中Linux命令中许多命令都是有对应的选项的,不同的选项对应不同的功能。那这个操作是如何实现的呢?main函数参数                 首先,让我们先来讲讲main函数。mian函数可以说是我们接触编程的第一步,但许多人可以对main函数并不了解。        main函数其实是可以有参数的。int main(int argc,char* argv[])AI生成项目         第一个参数,argc:表示命名行参数的数量(包括程序名本身)         第二个参数,argv:是一个字符指针数组,储存了命令行中的每个参数验证:#include<stdio.h> int main(int argc,char* argv[]){  for(int i=0;i<argc;i++)  {      printf("%s\n",argv[i]);                                                                                                                                            }}AI生成项目 实现指令选项#include<stdio.h>#include<string.h> int main(int argc,char* argv[]){    if(argc==2)    {           char* x=argv[1];        if(strcmp(x,"-a")==0)            printf("执行-a功能\n");        if(strcmp(x,"-b")==0)            printf("执行-b功能\n");        if(strcmp(x,"-c")==0)            printf("执行-c功能\n");    }       else        printf("请使用:-a/-b/-c\n");}AI生成项目         进程中有一张表:argv表(命令行参数表),利用此表则可以实现命令的选项的功能。认识环境变量首先我们要执行一个程序,必须先找到它而环境变量可以帮助我们找到其位置使用env指令我们可以查看系统中所有的环境变量,也可以使用echo $名称,打印对应的环境变量环境变量的格式为:名称=内容  PATH        我们先来认识一下PATH环境变量。        PATH环境变量表示查找指令位置的默认路径,比如当我们执行指令:ls、cd等等指令时,bash就会使用PATH环境变量去找到指令对应的程序并执行。        我们知道系统指令的实现也是C语言,但为什么系统自己的指令可以直接执行,而我们自己写的代码却必须要 “./” 作为前缀才可以执行,PATH环境变量就是关键。理解两个问题:1.环境变量是如何存储的呢?        bash下会形成一个环境变量表,存放各种各样的环境变量。(上面我们也讲到了bash中有一个命令行参数表,也是就是bash下有两张表)2.环境变量一开始是从哪里来的?        bash下的环境变量表是重系统配置文件中来的(以下便是配置文件)。 认识更多环境变量HOME        HOME环境变量,记录的是当前用户的家目录路径root@hcss-ecs-4ce7:~# echo $HOME/rootAI生成项目SHELL        表示当前使用的shell版本root@hcss-ecs-4ce7:~# echo $SHELL/bin/bashAI生成项目 USER        表示当前用户是谁root@hcss-ecs-4ce7:~# echo $USERrootAI生成项目 HISTSIZE        表示Linux中记录历史命令的最大数量(既Linux的历史命令最多记录HISTSIZE条)root@hcss-ecs-4ce7:~# echo $HISTSIZE1000AI生成项目HOSTNAME         表示当前主机的主机名root@hcss-ecs-4ce7:~# echo $HOSTNAMEhcss-ecs-4ce7AI生成项目PWD        记录当前所在的工作路径hyc@hcss-ecs-4ce7:~/linux/progress$ echo $PWD/home/hyc/linux/progressAI生成项目OLDPWD        记录上一次的工作路径hyc@hcss-ecs-4ce7:~/linux/progress$ echo $OLDPWD/home/hyc/linuxAI生成项目环境变量的获取方式指令env:查看全部的环境变量echo $xxx:查看某个环境变量expot:添加环境变量unset:删除环境变量 代码调用系统调用:getenv获取环境变量头文件为:#include<stdlib.h>返回值:获取成功返回指针,失败返回NULL#include<stdio.h>#include <stdlib.h> int main(){    char* str=getenv("PATH");                                                                                                                                              if(!str)        printf("获取失败");    else        printf("%s\n",str);}AI生成项目 环境变量特性1.环境变量具有全局性,可以被子进程继承,同一个bash下的进程都可以访问 。2.存在本地变量,只能在bash内部使用。如下:i 就是本地变量。 3.export添加环境变量,是添加在bash中的,再子进程继承达到全局可用。   export命令与其他命令不同:export命令是内键命令:既不需要创建子进程,bash自己调用函数执行(或系统调用完成)————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/huangyuchi/article/details/149715773
  • [技术干货] LM Studio 教程:通过暴露本地端口实现局域网内客户端调用(LM Studio 作为本地 LLM API 服务器)-转载
    links:https://lm-studio.cn/docs/apiLM Studio 可以像 Ollama 一样通过暴露本地端口实现局域网内客户端调用。以下是具体实现方式和技术细节:1. 核心功能支持LM Studio 的 开发者选项卡 提供了完整的本地 API 服务器功能,支持以下配置:端口暴露:默认使用 1234 端口,可自定义修改(需注意端口冲突问题)3局域网访问:勾选 “在局域网内提供服务” 选项后,服务器会监听所有网络接口(0.0.0.0),允许其他设备通过 IP 地址访问34跨域支持:启用 CORS 后,可对接网页应用或其他客户端工具32. 配置步骤实现局域网调用的具体操作流程:启动服务端:在 LM Studio 的 “开发者” 选项卡中选择模型勾选 “在局域网内提供服务” 并设置端口(如 1234)3点击 “启动服务” 按钮,控制台会显示运行日志和访问地址(如 http://<本机IP>:1234)3客户端调用:其他设备可通过以下方式访问:# 示例:使用 curl 调用聊天接口curl http://<本机IP>:1234/v1/chat/completions \  -H "Content-Type: application/json" \  -d '{"messages": [{"role": "user", "content": "你好"}], "model": "<模型名称>"}'AI生成项目bash1234支持 OpenAI 兼容的 API 格式(包括 /v1/chat/completions、/v1/completions 等端点)13防火墙配置(Windows 系统):需在防火墙入站/出站规则中开放对应端口(如 1234)53. 与 Ollama 的对比特性    LM Studio    Ollama协议兼容性    OpenAI API 兼容    自有 API 格式界面操作    图形界面一键配置    需命令行操作多模型支持    需手动加载不同模型    支持 Modelfile 动态切换日志管理    内置日志记录(可记录提示与响应)3    需自行配置日志工具4. 典型应用场景多人协作开发:团队成员通过局域网共享同一模型服务2跨设备调用:手机/平板通过浏览器或 Open WebUI 访问本地模型2自动化流程:对接 VS Code 插件或其他 AI 工具链1注意:如果遇到连接问题,需检查以下三点:LM Studio 服务端是否已勾选局域网选项防火墙是否开放对应端口客户端是否使用正确的 IP 地址(可通过 ipconfig/ifconfig 查看本机局域网 IP)35————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/a772304419/article/details/145800588
  • [技术干货] 【Linux篇章】穿越数据迷雾:HTTPS构筑网络安全的量子级护盾,重塑数字信任帝国-转载
    一· 认识HTTPSHTTPS 也是一个应用层协议. 是在 HTTP 协议的基础上引入了一个加密层,即可以理解成SSL+TLS+HTTP构成!二· 为何要加密所以:因为 http的内容是明文传输的,明文数据会经过路由器、wifi热点、通信服务运营商、代理服务器等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了。劫持者还可以篡改传输的信息且不被双方察觉,这就是中间人攻击,所以我们才需要对信息进行加密。总之,早期的http在网络中明文传递,是暴露的,能被修改!!!其中:加密就是把明文(要传输的信息)进行一系列变换,生成密文!解密就是把密文再进行一系列变换,还原成明文!在这个加密和解密的过程中,往往需要一个或者多个中间的数据,辅助进行这个过程,这样的数据称为密钥(正确发音yue 四声,不过大家平时都读作yao四声)!因此:HTTPS 就是在 HTTP 的基础上进行了加密,进一步的来保证用户的信息安全!三. 加密方式3.1 对称加密采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密常见对称加密算法(了解):DES、3DES、AES、TDEA、Blowfish、RC2等特点:算法公开、计算量小、加密速度快、加密效率高!因此:我们只需要记住:加密秘钥和解密秘钥是同一把,这在某种意义上成为了它的缺点!一个简单的对称加密:按位异或:假设明文a= 1234,密钥key = 8888则加密a ^ key得到的密文b 为9834,然后针对密文9834再次进行运算b ^key,得到的就是原来的明文1234.(对于字符串的对称加密也是同理,每一个字符都可以表示成一个数字)当然,按位异或只是最简单的对称加密.HTTPS中并不是使用按位异或.3.2 非对称加密需要两个密钥来进行加密和解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。常见非对称加密算法:RSA,DSA,ECDSA。特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。最大的缺点就是运算速度非常慢,比对称加密要慢很多。因此:这里只需要记住公钥加密只有私钥解开,私钥加密只有公钥解开!3.3 数据摘要与数据指纹数字指纹(数据摘要),其基本原理是利用单向散列函数(Hash函数)对信息进行运算,生成一串固定长度的数字摘要。数字指纹并不是一种加密机制,但可以用来判断数据有没有被篡改。摘要常见算法:有 MD5、SHA1、SHA256、SHA512等,算法把无限的映射成有限,因此可能会有碰撞(两个不同的信息,算出的摘要相同,但是概率非常低)。摘要特征:和加密算法的区别是,摘要严格意义不是加密,因为没有解密,只不过从摘要很难反推原信息,通常用来进行数据对比,比如数据库管理使用摘要。四. HTTPS加密方式4.1 只使用对称加密也就是我们只使用一把秘钥进行通信:理想:实际:这种理想上被中间人截取的也只能是密文,无利用价值,但是服务端和每个客户端都要有个秘钥来维护,这样就比较麻烦,因此一般采取的是先发送秘钥进行协商,但是这样就被中间人获取秘钥了,因此这种方案是不可行的!因此就导致了这样:4.2 双方使用一个非对称加密服务端发送信息前提是先要和浏览器协商(把公钥发给它)然后用私钥加密,给浏览器,这样信息就暴露了(服务器到浏览器的信息暴露),但是浏览器拿到后,进行发送用公钥加密(只能私钥解密),此时无法暴露,服务器就能正常收到信息!比如:4.3 双方都使用非对称加密交换公钥,公钥加密,私钥解密!它们俩互相交换了公钥(用于加密),有没有可能中间人也用公钥进行加密然后比如从客户端发给服务端,这样服务端也就分不清了或者中间人用自己的公钥给客户端,然后进行信息窃取!总结:因为非对称加密:效率低,而且还有安全问题!4.4 非对称加密+对称加密也就是服务端先把公钥给客户端,客户端用它进行对称密钥加密然后给服务端(中间人无私钥,无法解密),最后他俩就用这个对称公钥进行加密!过程如图所示:这样看似是没有毛病的,但是有没有可能中间人把自己的公钥m给client,然后不就得到了这个被m加密的x了,然后再用s进行加密给服务端这个x,之后s与c用x通信不就被中间人掌握了吗!结局就成了这样:因此:关键问题还是防止中间人攻击也就是如何确保client收到的公钥来自server而不是中间人呢?4.5 两次非对称加密与一次对称加密引入证书这种方式的前两次非对称加密都是为了保证最后一次对称加密的安全性!证书=签名+明文CA认证:服务端在使用 HTTPS 前,需要向 CA 机构申领一份数字证书,数字证书里含有证书申请者信息、公钥信息等。服务器把证书传输给浏览器,浏览器从证书里获取公钥就行了,证书就如身份证,证明服务端公钥的权威性!下面看一下申请证书过程图:这里需要记住:签名只有CA机构能做(因为只有它才有对应私钥)。签名和解签名的秘钥和服务端的秘钥不是同一种。CA的私钥由CA使用进行签名,CA的公钥由浏览器自备进行解签名验证。服务端和客户端通信的时候,客户端会先请求,此时服务端就去CA机构申请一个证书,这个证书就是保证client拿到的一定是server的公钥!证书有没有可能被篡改那么此时浏览器拿到证书如何核对有没有在server与client传播过程中被中间人修改呢?CA机构在形成证书的时候会拿着私钥对明文信息散列后的摘要进行加密作为签名!然后当客户端收到证书后会拿着CA公钥对签名解密拿到的数据和明文信息散列后进行对比;如果相同就证明没有被修改,拿着公钥进行传递对称秘钥!!!被修改了就警告! !! —>保证了client一定拿到server的公钥!!!如图:但是如果中间人修改server发给client的证书的明文/签名/或者整体替换呢(中间人如果要想成功传递自己的秘钥必须修改签名)?修改明文:如果修改了的话,当client拿到证书后会发现不匹配的。修改签名:中间人没有ca的私钥无法对前面修改。整体替换:这里整体替换就必须要ca的严格审查,中间人只能按照要求申请新的证书,但是还有域名等等一些信息使得客户端可以识别到是不是对应服务端!中间人动机:要么把明文里的公钥换成自己的。要么换成自己的后然后改签名让客户能够获取公钥。但是,均行不通!永远记住:中间人没有CA私钥,所以对任何证书都无法进行合法修改,包括自己的。因此总结:中间人无法修改证书和伪造新的证书!非对称加密+对称加密+证书认证因此,它来了!这里可以理解成:首先进证书申请+客户端验证证书(第一次非对称加密以及解密);接着就是client拿到对应的server的公钥,然后进行传递接下来进行非对称加密的秘钥,服务端然后拿着自己的私钥进行解密拿到(第二次非对称加密以及解密);接着就是用这个非对称秘钥进行通信了(非对称加密及解密)!也就是这样:注意:这里的易混点是签名和解签名只能用CA的私钥和公钥;而不是服务器的的私钥公钥(过程中服务器会泄露公钥但私钥不会泄露)!! !下面看张更加详细的图:详细步骤:第一次非对称加密:用于校验证书是否被篡改.服务器持有私钥(私钥在形成CSR文件与申请证书时获得),客户端持有公钥(操作系统包含了可信任的CA认证机构有哪些,同时持有对应的公钥).服务器在客户端请求时,返回携带签名的证书.客户端通过这个公钥进行证书验证,保证证书的合法性,进一步保证证书中携带的服务端公钥权威性。第二次非对称加密:用于协商生成对称加密的密钥.客户端用收到的CA证书中的公钥(是可被信任的)给随机生成的对称加密的密钥加密,传输给服务器,服务器通过私钥解密获取到对称加密密钥。对称加密:客户端和服务器后续传输的数据都通过这个对称密钥加密解密。超详细大白话叙述过程:首先服务器拿着相关信息(域名,csr,明文信息等),然后生产对应的私钥(自己保存),公钥(交给CA)然后向CA申请证书,ca检查严格审核后,拿着自己的专属私钥进行对明文散列后数据签名,接着交给server,最后server把证书给client,client拿着自己内置信任的CA机构的公钥容纳后进行解密签名看是否合法(明文散列是否相同,域名等是否匹配),不合格就发出警告(上报,或者浏览器阻止用户访问等),合格的话就拿到对应服务器的公钥,自己产生对称秘钥,然后发给服务端(中间人稚嫩获得密文,无法解密),然后服务端解密后,他俩就利用对称秘钥进行通信了!总结:这一切的目的就是保证这个对称秘钥通信的时候是安全的 !疑问点为什么要进行签名加密:如果不签名的话就是直接证书上的就是明文,那么在客户端核对前是有可能被中间人修改的,因此是不安全的。为什么明文不直接加密而是先生成摘要呢:缩小签名密文的长度,加快数字签名的验证签名的运算速度。如何成为中间人:ARP欺骗:在局域网中,hacker经过收到ARP Request广播包,能够偷听到其它节点的(IP,MAC)地址。例,黑客收到两个主机A,B的地址,告诉B(受害者),自己是A,使得B在发送给A的数据包都被黑客截取。ICMP攻击: 由于ICMP协议中有重定向的报文类型,那么我们就可以伪造一个ICMP信息然后发送给局域网中的客户端,并伪装自己是一个更好的路由通路。从而导致目标所有的上网流量都会发送到我们指定的接口上,达到和ARP欺骗同样的效果。查看浏览器的受信任证书相关信息首先点击浏览器对应设置:接下来点击安全选项:可以看到有很多信任的证书,点击可以看到公钥等信息:五. 本篇小结通过本篇对HTTPS的基础认识,建立了大致的了解,知道了它背后的原理等,此次学习虽然是大致的,但是也看到了HTTPS背后的基本原理,为以后对它更加深入的学习奠定了基础,冲冲冲!————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2401_82648291/article/details/147658666