《深入Linux内核架构》第2章 进程管理和调度 (1)

目录

前言

2.1 进程优先级

2.2 进程生命周期

2.3 进程表示

2.3.1 进程类型

2.3.2 命名空间

2.3.3 进程ID号

2.3.4 进程关系

2.4 进程管理相关的系统调用

2.4.1 进程复制

2.4.2 内核线程

2.4.3 启动新程序

2.4.4 退出进程


前言

本章内容太多,分为两篇博文。这是第一部分(1)

root用户的UID=0。

两个系统调用:

        chroot函数:

                含义:更改当前进程的根目录,限制进程只能访问新根目录下文件。

                作用:

                        安全隔离,即使进程被攻击,也无法访问系统的其他部分。

                        在开发测试中,用来创建一个独立的文件系统环境,而不会影响主系统。

        chdir函数:

                含义:更改进程当前工作目录,影响了相对路径解析起点,但不限制文件访问范围。

                作用:简化路径解析,方便文件操作。

ELF:(Executable and Linking Format)

        Linux中可执行文件或共享库的标准格式。

2.1 进程优先级

进程分类:

        硬实时进程:

                含义:必须在预定时间内完成任务。

                使用场景:航空航天、汽车控制、工业自动化等。

                        Linux不支持,而RTLinux支持。

        软实时进程:

                尽力在预定时间内完成任务。Linux支持。

        普通进程:

                大部分进程都是,Linux支持。

实时进程的优先级比普通进程高。

而进程的优先级最终决定了进程运行的时间片比例,和调度先后顺序。

每个进程都有各自内核栈,用于存储该进程在内核态执行的函数参数、局部变量、寄存器等数据。

        内核栈大小为THREAD_SIZE,通常为8K。

不同进程的内核栈不会重叠,但都在地址空间3-4G范围内(32位系统)。

2.2 进程生命周期

5个进程的状态:

        运行态(R):

                已在CPU的运行队列上,但不一样正在CPU上运行。

        可中断休眠(S):

                被阻塞,等待到资源就绪后,被唤醒。

        不可中断休眠(D):

                深度休眠,不被中断。可确保关键操作的完整和可靠,

                使用场景:保证重要磁盘IO操作不被中断。

        停止态(T):

                SIGSTOP信号可停止进程。SIGCONT信号让进程继续运行。

                使用场景:gdb断点。

        僵尸态:

                已终止但未被父进程使用wait() 或 waitpid()处理。

                僵尸进程占用了系统的进程表项,耗尽资源。

kernel preemption:内核抢占

2.3 进程表示

内核用struct task_struct结构体表示一个进程。

struct task_struct {volatile long            state; 进程状态:运行态,僵尸态,停止态,可中断休眠,不可中断休眠void                     *stack;    指向该进程的内核栈的顶部int                       prio,static_prio,normal_prio;     进程优先级,2.5章节详细unsigned int              rt_priority;            实时调度器使用unsigned int              policy;        调度策略:如SCHED_NORMAL/SCHED_RRstruct sched_class        *sched_class;            一个调度器的函数指针struct sched_entity       se;                调度信息,嵌入到CFS调度红黑树cpumask_t                 cpus_allowed;        允许进程在哪些CPU上运行struct mm_struct          *mm, *active_mm;    该进程的进程地址空间(用户空间)pid_t                     pid;                pid_t                     tgid;                即getpid函数返回值struct task_struct _rcu   *real_parent;struct task_struct __rcu  *parent;                父进程struct pid_link           pids[PIDTYPE_MAX];struct fs_struct          *fs;                文件系统信息struct files_struct       *files;        包含该进程所有文件描述符struct nsproxy            *nsproxy;        命名空间struct signal_struct      *signal;        信号描述信息struct sighand_struct     *sighand;        信号处理函数,线程组间可共享信号处理函数。
}

task_struct成员介绍:

        struct task_struct *real_parent;

                当ptrace跟踪进程时,该进程的parent指向ptrace进程,所以需用real_parent保存真实父进程。

        struct mm_struct *active_mm;

                当内核线程抢占用户进程后,内核线程的mm成员为NULL,需要通过该变量可知道抢占了哪个用户进程。

图片总结:

task_struct 中

        struct fs_struct *fs;

        struct files_struct *files;

进程state为TASK_UNINTERRUPTIBLE的进程不能被信号唤醒,只能内核亲自唤醒。

一个进程打开最大文件数目默认是1024。

       查看限制:ulimit -n

        统计该进程已经打开文件数目:ls /proc//fd | wc -l

资源限制

struct rlimit{

        unsigned long rlim_cur; 软限制。超过软限制但未到硬限制时,会收到警告。

        unsigned long rlim_max; 硬限制。强制不能超过硬限制。

}

struct rlimit被谁包含:

        struct task_struct 中成员s truct signal_struct *signal;

        struct signal_struct中成员struct rlimit rlim[RLIM_NLIMITS];

如:

        一个进程能创建的最大文件大小:

                current->signal->rlim[RLIMIT_FSIZE].rlim_cur

init进程可拥有的最大子进程数量:

        init_task.rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;

        全局变量max_threads:把八分之一可用内存用于管理线程信息时,可创建最多线程数目。

相关命令:

#cat /proc/259/limits Limit               Soft Limit        Hard Limit        Units  Max cpu time        unlimited         unlimited         seconds   Max file size       unlimited         unlimited         bytes     Max stack size      8388608           unlimited         bytes     Max open files      1024              4096              files     Max pending signals 257417            257417            signals   

setrlimit系统调用:

        作用:设置该进程的资源限制。如:

                最大CPU时间,最大文件长度,数据段最大长度,用户栈最大长度

                打开文件最大数目,待决信号最大数目,最大实时优先级,

                不可换出页最大数目(mlock)

int mlock(const void *addr, size_t len);

        将进程指定的内存区域锁定在物理内存中,防止被内核交换出去,可保证该内存区域访问速度更快。

        使用场景:高性能的应用程序,比如实时应用程序、高性能数据库等。

SIGQUEUE_MAX:待决信号队列大小。默认32或64,高性能场景值更大,是系统级参数,对所有进程生效。

2.3.1 进程类型

2.3.2 命名空间

命名空间部分稍显复杂。

命名空间(Namespace)和控制组(Cgroups):

用于实现进程隔离和资源限制。

命名空间可用于容器技术,如Docker

如何创建新命名空间:

        1. fork时根据标志是共享命名空间,还是新建命名空间。

                带有下面标志表示不共享:

                        CLONE_NEWUTS,CLONE_NEWIPC,CLONE_NEWUSER

                        CLONE_NEWPID,CLONE_NEWNET

        2. unshare系统调用:用于取消共享命名空间,创建新的命名空间,起隔离作用。

                unshare(CLONE_NEWNET);

实现:

struct task_struct {            struct nsproxy     *nsproxy;      该进程的命名空间。如上图多个进程可共享同一命名空间。}struct nsproxy {    atomic_t                     count;struct uts_namespace         *uts_ns;     //UTS命名空间包含:内核版本、底层体系等。struct ipc_namespace         *ipc_ns;     struct mnt_namespace         *mnt_ns;    struct pid_namespace         *pid_ns;    pid命名空间struct net                   *net_ns;
}

所有进程在创建时,默认为init_nsproxy命令空间。

        struct nsproxy init_nsproxy = {

                .uts_ns = &init_uts_ns,

                ...

                .pid_ns = &init_pid_ns,

        };

        所有进程默认pid命名空间是nit_pid_ns,level为0。

UTS命名空间:

        即struct uts_namespace。

        不需特别处理,是简单信息,没有层次和父子关系。

        UTS:UNIX Timesharing System

struct uts_namespace{struct kref            kref;            该结构实例引用计数struct new_utsname     name;
};struct new_utsname {        就是系统信息char     sysname[65];        //不能修改,都是"Linux"char     nodename[65];char     release[65];char     version[65];char     machine[65];
};

cgroup:Control Group

        将一组进程放入一个cgroup,并为该cgroup分配资源限制,如CPU、内存、磁盘 I/O 、网络带宽等资源。

        适用于容器化、虚拟化、多租户环境。

cgroups有几个子系统:

        内存,CPU,net,ns,磁盘I/O

限制CPU资源配置举例:

        1. 创建一个名为"my_cgroup"的Cgroup

                # sudo mkdir /sys/fs/cgroup/cpu/my_cgroup

    

        2. 将CPU配额设置为50毫秒,周期设置为100毫秒

                quota配额:即一个周期内,cgroup中的进程能够使用CPU的总时间量。

                # echo 100000 > /sys/fs/cgroup/cpu/my_cgroup/cpu.cfs_period_us

                # echo 50000 > /sys/fs/cgroup/cpu/my_cgroup/cpu.cfs_quota_us

        3. 将指定进程移到该Cgroup中

                # echo > /sys/fs/cgroup/cpu/my_cgroup/tasks

2.3.3 进程ID号

PID 0:swapper或idle进程

PID 1:init 进程,其他进程都由init进程fork而来。

在内核中,一个进程和线程都用task_struct表示。

getpid()和ps命令其实返回的是该进程task_struct中tgid成员,而不是pid成员

        tgid:thread group id,线程组ID,即线程组中组长的线程id。

一个进程的所有线程有各自task_struct,但线程组ID(TGID)一样。

单进程(没有线程)时task_struct 中tgid和pid相等。

一个线程的task_struct->group_leader = task_struct (组长线程)

int setpgrp(void);

        将调用进程设置为新进程组的组长。

pid_t setsid(void);

        创建新会话,并将调用进程设置为该会话组的组长(session leader)。

        作用:创建守护进程时,setsid可脱离当前终端会话的控制,在后台独立运行。

同一会话组中所有进程的sid相同。

TGID:线程组ID,直接保存在task_struct中,即task_struct中tgid成员。

会话组和进程组ID:不直接包含在task_struct中,而但保存在信号处理结构中。

        进程组ID:task_struct->signal->_pgrp

        会话组ID:task_struct->signal->session

同一进程在不同命名空间中PID不一样:

        全局ID:init命名空间和内核可见。

        局部ID:属于某个特定的命名空间。

struct task_struct  {pid_t                 pid;                     //全局PIDpid_t                 tgid;                    //全局tgid= thread group id struct nsproxy        *nsproxy;                //默认是init_nsproxy命名空间struct pid_link       pids[PIDTYPE_MAX];       //有三个链表,PID/PGID/SID    
}

1. struct nsproxy init_nsproxy 全局默认命名空间

        struct nsproxy init_nsproxy = {

                .....

                .pid_ns = &init_pid_ns,

                .....

        };

        struct pid_namespace init_pid_ns;

        struct pid_namespace

        {

                struct pidmap pidmap[PIDMAP_ENTRIES]; 位图,表示pid是否已分配

                struct task_struct *child_reaper; //当前命名空间的init进程,即用于wait孤儿进程的进程

                unsigned int level; 所处第几层命名空间,如父命名空间=0,子level=1,孙level=2

                struct pid_namespace *parent; 父命名空间

}

2. struct pid_link

struct task_struct  {struct pid_link         pids[PIDTYPE_MAX];    //有三个链表,PID/PGID/SID    
}

struct pid_link
{struct hlist_node      node;    //该字段帮助使pid通过链表遍历访问到task_structstruct pid             *pid;    //该指针直接指向pid
};

为了支持pid命名空间,内核增加了pid和upid结构体,upid中nr即是pid

同一个struct pid可以被不同的stask_struct使用。

struct pid                //内核对PID的内部表示,无需命名空间
{atomic_t             count;          //该结构体的引用计数,同一个pid结构可被多个进程共享unsigned int         level;          //pid所在的层级struct hlist_head    tasks[PIDTYPE_MAX];   //多个链表头,连接同一ID的所有task_structstruct upid          numbers[1];     //内核视图下,子/孙/曾孙命名空间都对应一个upid结构体
};

struct upid {            //特定子命名空间中可见,需要指定命名空间int                     nr;            //该命名空间中pid(即局部ID)struct pid_namespace    *ns;            该pid命名空间struct hlist_node       pid_chain; 当hash冲突时,用于连接同一hash表数组所有冲突upid实例
};

总结图:

全局pid_hash,哈希表数组。

        作用:通过PID值找到对应struct pid,再通过struct pid最后找到task_struct。

如上图可知,在不同level时,pid可以相同。

PID分配器(pid allocator)

        bitmap位图:用于检测某pid是否已分配。

struct pid *task_pid(struct task_struct *task){return task->pids[PIDTYPE_PID].pid;}

获取线程组的pid结构:

        task->group_leader->pids[PIDTYPE_PID].pid;

如何根据pid结构体来获取数字id?

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns){struct upid *upid;pid_t nr = 0;if (ns->level level) { 跟踪该struct pid 更顶层命令空间对应pid,所以pid->level更大。upid = &pid->numbers[ns->level];if (upid->ns == ns)nr = upid->nr;}return nr;
}

alloc_pidmap:分配一个新PID位图,用于跟踪哪些pid空闲。

2.3.4 进程关系

父进程通过children成员连接到子进程。

子进程之间通过sibling成员连接。

2.4 进程管理相关的系统调用

2.4.1 进程复制

1. _do_fork函数

        fork vfork clone都最终调用 _do_fork

                clone:通过CLONE_XX标志精确控制共享哪些资源)

                vfork:由于fork使用了COW技术,vfork优势不再,使用少。

COW:copy-on-write,写时复制。

        

fork子进程时,使用COW机制,原理:

        不复制父进程的地址空间。而是将父进程的地址空间标记为只读,并与子进程共享相同的物理内存页。

        当父进程或子进程有写内存时,发生缺页异常。

       异常处理中检查该页是否可以写,若可以写则复制修改的内存页再修改子进程页表项。若不可以段错误。

COW页:避免不必要的拷贝,提高性能。

2. 执行系统调用

long do_fork(unsigned long clone_flags,unsigned long stack_start,unsigned long stack_size,int __user *parent_tidptr,int __user *child_tidptr)stack_start:用户栈parent_tidptr,child_tidptr:用于返回线程ID给用户空间,因为pthread_create函数需要tid值


系统调用在用户空间和内核空间传递参数的方法因体系结构而异:

        寄存器传递:速度块,但寄存器数量有限。

        栈传递:可传递内容多。

3. do_fork的实现

copy_process:见下节

wake_up_new_task:将该新进程加入调度器队列。

4. copy_process 复制进程

dup_task_struct:

        复制父进程task_struct和thread_info结构体

thread_info:也存储重要线程信息,只是各体系架构定义不一样。从task_struct中独立出来。

        通常包含:内核栈栈顶,指向当前线程的task_struct等。

创建新进程时分配了新的内核栈,即task_struct->stack

复制后,父子进程两个的task_struct结构体只有一个成员不同:

        新进程分配了一个自己的内核栈,即task_struct->stack

union thread_union {struct thread_info thread_info; 定义在不同体系中unsigned long stack[THREAD_SIZE/sizeof(long)];
};

THREAD_SIZE=8K,即上图内核栈最大为8K,恶意操作内核栈可能覆盖thread_info

struct thread_info {            //以arch/arm为例unsigned long            flags;            int                      preempt_count;    抢占计数,表示当前线程是否可被抢占。struct task_struct        *task;            代表当前线程__u32                     cpu;                当前线程所在CPU    struct cpu_context_save   cpu_context;    保存着CPU寄存器(如PC,SP等)
};其中thread_info中flag有:TIF_SIGPENDING 当前进程是否有待决信号TIF_NEED_RESCHED 当前进程想让出CPU,调度器选择其他进程执行。TIF = Thread Info Flag

如何访问指定线程的thread_info?

        (struct thread_info *) (task)->stack

如何根据当前线程thread_info找到当前线程的task_struct?

        task_struct *current = current_thread_info()->task

如何访问当前线程的thread_info?

struct thread_info *current_thread_info(void)        ARM为例
{register unsigned long sp asm ("sp");        //sp寄存器:保存了当前线程的内核栈顶部return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}

  

如何根据thread_info找到对应task_struct?

        task_struct *current = current_thread_info()->task

task_struct->stack和CPU sp寄存器,如上图,两者不指向同一地址:

        task_struct->stack:

                指向创建该线程时分配8K内核栈的起始地址。也就是thread_info处

        CPU sp寄存器:

                当前CPU运行线程的内核栈栈顶。

当前进程正在运行时:

        通过ARM sp寄存器值,得到当前线程的thread_info,再得到current的task_struct。

进程切换到一个新进程时:

        通过task_strcut -> stack,得到该线程的thread_info,再通过thread_info得到cpu_context,即可得到该进程上次执行时的寄存器信息,如pc,sp,r0-r12等。

进程切换时,关于进程的task_struct的stack成员,sp寄存器,变化过程?

1. 保存当前进程的上下文:

        保存当前进程上下文到内核栈中:包括CPU的通用寄存器、程序计数器PC、栈指SP等。

2. 切换新进程的:

     切换到新进程的task_struct结构体,再通过task_struct->stack得到thread_info。

3. 恢复新进程上下文

        从thread_info中cpu_context得到该进程上次执行时的上下文信息。如pc,sp,r0-r12等。从而恢复新进程上下文值。此时可正确得到新进程的内核栈栈顶sp。

struct pt_regs 和 thread_info中struct cpu_context_save 是用于保存 CPU 寄存器状态

区别:

        struct pt_regs:用于处理异常或系统调用返回时将其恢复到原始状态,还可传参。

        struct cpu_context_save:用于进程切换时主动保存CPU上下文。

kstack_end(void *addr)函数:

        返回当前线程的内核栈的结束地址。

                这样就可判断某个地址是否在内核栈区间。

继续回到copy_process

sched_fork函数:

        1. 初始化子进程调度参数:优先级和调度策略等。

        2. 复制父进程的调度器相关数据(调度器类别,时间片)。

        3. 将子进程加入调度队列。

copy_process会检测如下标志:

        CLONE_FS 共享父进程的文件系统

        CLONE_NEWXX 不共享的资源

        CLONE_FILES 共享父进程的文件描述符

        CLONE_SIGHAND 共享父进程的信号处理函数

        CLONE_MM COW,只复制页表

struct pt_regs { 如上图,存储在当前线程的内核栈最底部中。

        long uregs[18];

};

struct pt_regs作用:

        从用户态陷入内核态时候,用户态的上下文信息保存在pt_regs数据结构中。还可传递系统调用参数和返回值。

存储的寄存器信息有:

        #define ARM_cpsr uregs[16] 程序状态寄存器

        #define ARM_pc uregs[15]

        #define ARM_lr uregs[14]

        #define ARM_sp uregs[13] 当前线程内核栈的栈顶

        #define ARM_ip uregs[12]

        #define ARM_fp uregs[11]

        #define ARM_r10 uregs[10] //通用寄存器 r0-r10

struct pt_regs这18个寄存器,保存在当前线程的内核栈的底部,如上图。

        即 :struct pt_regs *regs = task_struct->stack + THREAD_START_SP - 1

copy_process还调用copy_thread。

        copy_thread重要内容:

                填充thread_info和pt_regs。

父子进程可共享信号处理函数,但不共享挂起待处理信号。

unsigned long put_user(void __user *dst, const void *src, unsigned long size);

        向用户空间传递单个数据。如char,short,int大小的数据,比copy_to_user快。

copy_to_user优点:可复制任意类型和长度数据。

每个体系的虚拟地址0到4KB的区域,没有任何意义。可重用该地址范围来编码错误码。

如果返回值指向0-4KB地址范围内部,表示该调用失败,其原因由指针值判断。

宏ERR_PTR:将数值常数编码为指针。

使用方法:return ERR_PTR(-EINVAL);

2.4.2 内核线程

内核线程父进程是:init进程

内核线程的任务通常是周期任务,如:

        pdflush:刷新脏页到磁盘。

        kswapd:回写内存页到交换区。

        ksoftirqd:处理软中断。

创建内核线程:

        pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)

        最终也调用_do_fork(CLONE_VM)

创建的内核线程在指定CPU上运行:

        kthread_create_on_cpu()

                -> p->sched_class->set_cpus_allowed(p, new_mask);

kthread_run() = kthread_create() + wake_up_process()

内核线程不需要用户空间,所以内核线程task_struct的mm_struct=NULL。

当内核线程运行,可不置换掉之前进程的用户空间地址,因为内核线程不使用用户空间。所以用active_mm保存用户空间mm_struct,因为内核线程运行后调度的进程通常还是之前那个用户进程,通过active_mm直接恢复,不用修改映射表,TLB中缓存的映射表仍然有效。这叫惰性TLB。

惰性TLB:一种优化策略,延迟或避免不必要TLB的更新,提高性能。

TASK_SIZE:即用户态虚拟地址大小(32位,0-3G)。

        内核线程地址空间大于TASK_SIZE。

2.4.3 启动新程序

execve系统调用

int do_execve(struct filename *filename, const char __user *const __user *__argv, const char __user *const __user *__envp)

会__user定义的指针进行参数检查。

linux_binfmt存储了所有注册的可执行程序的加载函数和执行函数。

struct linux_binprm:保存可执行文件的信息,包括可执行程序的路径,参数和环境变量的信息,vma

struct linux_binfmt {struct list_head lh;         连接所有二进制的执行函数int (*load_binary)(struct linux_binprm *); 加载二进制文件int (*load_shlib)(struct file *); 加载动态库int (*core_dump)(struct coredump_params *cprm); 用于crash时核心转储文件}

Linux文件特殊权限SUID、SGID、Sticky总结:

SUID文件所属主:Set User ID

        当一个可执行文件具有SUID权限时,它执行时临时具有文件所有者的权限,而不是执行者的权限。

        作用:暂时提升用户权限。允许普通用户执行root用户的程序。

        缺点:潜在安全性威胁。谨慎使用。

        使用举例:

                /usr/bin/passwd:允许用户更改自己的密码而无需root权限。

        设置方法:

                增加suid权限:chmod u+s ,或chmod 4755

                移除suid权限:chmod u-s ,或chmod 0755。

SGID文件属组: Set Group ID

        当一个文件或目录设置SGID权限后,任何用户执行该文件或访问该目录时,都以该文件或目录所属的组身份执行,而不是该用户的组权限。

        使用场景:当不同组的用户在一个共享目录下创建新文件,新文件是该目录所属组的权限,而不是创建文件的用户的组权限。可确保所有用户以相同的组权限执行该目录下新文件。

        设置方法:

                增加suid权限:chmod g+s ,或chmod 2755。

                移除sgid权限:chmod g-s ,或chmod 0755。

Sticky权限:

        作用:一般用于目录,只允该目录下的文件的创建者删除自己的创建的文件,不允许其他人删除文件。

二进制文件起始处的magic值可标识该文件类型。

        如:ELF可执行文件:Magic number: 0x7F ELF

                JPEG图像文件:Magic number:0xFFD8FF

search_binary_hander:

        根据文件起始处的magic值来查找对应二进制文件的加载,执行函数。

二进制加载函数: 将文件段映射到虚拟地址空间。

        最终给变量start_code,end_code,start_data,end_data,start_brk brk,start_stack,arg_start,arg_end赋值。

每种二进制格式通过register_binfmt注册:

        如script_format,elf_format,aout_format等

2.4.4 退出进程

exit

各种引用计数减1。减1后若等于0,释放资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/742485.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

游戏开发中的坑之十三 Lut贴图相关问题

1.网上下载的或者游戏截帧得到的Lut贴图贴上之后可能效果如下,需要在PS里垂直方向反转一下贴图。 2.相关设置: (1)取消勾选sRGB; (2)像素为1024x32或者512x16; (3&#…

【机器学习300问】36、什么是集成学习?

一、什么是集成学习? (1)它的出现是为了解决什么问题? 提高准确性:单个模型可能对某些数据敏感或者有概念偏见,而集成多个模型可以提高预测的准确性。让模型变稳定:一些模型,如决策…

酷开科技以消费者需求为导向冲刺OTT行业的星辰大海

通过大屏营销、互动营销等方式,提升品牌认知度和市场竞争力。酷开科技始终坚持以消费者的需求为导向,致力于为品牌方和消费者搭建高效、准确的沟通桥梁,开创OTT大屏营销新纪元。 伴随技术发展,智能电视已经从“尝鲜”变成了主流产…

.NET MAUI 社区工具包 2023 年亮点

作者:Kym Phillpotts 排版:Alan Wang 2023 年已经过去了,让我们花点时间回顾一下 .NET MAUI Community Toolkit 项目的历程以及展望接下来的发展。作为 .NET MAUI 的配套产品,该开源库为开发人员提供了一组丰富多样的控件、转换器…

Axure基础 各元件的作用及介绍

图像热区 增加按钮或者文本的点击区域,他是透明的,在预览时看不见。 动态面板 用来绘制一下带交互效果的元件,他是动态的,如轮播图,一个动态面板里可以有多个子面板,每一个子面板对应着不同的效果。 他…

【研发日记】,Matlab/Simulink开箱报告(十)——Requirements Toolbox

前言 见《开箱报告,Simulink Toolbox库模块使用指南(五)——S-Fuction模块(C MEX S-Function)》 见《开箱报告,Simulink Toolbox库模块使用指南(六)——S-Fuction模块(TLC)》 见《开…

JavaEE:网络编程

网络编程:通过代码完成基于网络的跨主机通信 跨主机通信方式: 1.TCP/IP网络 2.蓝牙通信 3.近场通信NFC 4.毫米波通信:功率高,带宽高,抗干扰能力差 其中TCP/IP网络是日常编程中最常涉及到的,最通用的跨主机通…

安卓六大布局

LinearLayout(线性布局) 1.简介 线性布局在开发中使用最多,具有垂直方向与水平方向的布局方式。LinearLayout 默认是垂直排列的,但是可以通过设置 android:orientation 属性来改变为水平排列。 2.常用属性 orientation&#xf…

Linux编程4.3 网络编程-数据封装

1、数据封装 2、Internet协议(IP) IP的主要目的是为数据输入/输出网络提供基本算法,为高层协议提供无连接的传送服务。这意味着在IP将数据递交给接收站点以前不在传输站点和接收站点之间建立对话(虚拟链路)。它只是封…

【网络工程师进阶之路】BFD技术

个人名片:🪪 🐼作者简介:一名大三在校生,喜欢AI编程🎋 🐻‍❄️个人主页🥇:落798. 🐼个人WeChat:hmmwx53 🕊️系列专栏:&a…

软考75-上午题-【面向对象技术3-设计模式】-设计模式的要素

一、题型概括 上午、下午题(试题五、试题六,二选一) 每一个设计模式都有一个对应的类图。 二、23种设计模式 创建型设计模式:5 结构型设计模式:7 行为设计模式:11 考试考1-2种。 三、设计模式的要素 3…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的行人跌倒检测系统(深度学习+UI界面+完整训练数据集)

摘要:开发行人跌倒检测系统在确保老年人安全方面扮演着至关重要的角色。本篇文章详尽地阐述了如何利用深度学习技术构建一个行人跌倒检测系统,并附上了完整的代码实现。该系统采用了先进的YOLOv8算法,并对YOLOv7、YOLOv6、YOLOv5等先前版本进…

王道OnlineJudge 14

题目 二叉树层次建树就是一层一层的建树,从左到右。随着纵向层次的深入,结点的数量变化规律为:1→2→4→8→16→32。 先画图,然后看图可闭眼写代码 右边为辅助队列,有多少个二叉树结点,就有多少个辅助队…

【JavaScript】数据类型转换 ① ( 隐式转换 和 显式转换 | 常用的 数据类型转换 | 转为 字符串类型 方法 )

文章目录 一、 JavaScript 数据类型转换1、数据类型转换2、隐式转换 和 显式转换3、常用的 数据类型转换4、转为 字符串类型 方法 一、 JavaScript 数据类型转换 1、数据类型转换 在 网页端 使用 HTML 表单 和 浏览器输入框 prompt 函数 , 接收的数据 是 字符串类型 变量 , 该…

爆肝整理万能sass框架:react18+webpack5+typescript+ant Design,框架在手,交付无忧!!!

来活了,要求一周时间内快速给xxx业务开发一个sass系统平台,要求有角色权限控制,推荐模块,各种业务内容模块,莫慌,直接上代码!!!!! 1.系统框架配置…

带你摸透C语言相关内存函数

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话: 知不足而奋进,望远山而前行&am…

Windows10/11配置WSL(Ubuntu)环境

文章目录 WSL介绍WSL部署扩展:辅助工具Windosw Terminal安装下载 WSL介绍 传统方式获取Linux操作系统,是安装完整的虚拟机及镜像环境,例如虚拟机VMware 而使用WSL,可以以非常轻量化的方式,得到Linux系统环境 它无需单独虚拟一套硬…

校园兼职无忧网-创业计划书(附下载)

校园兼职无忧网是一个致力于为在校大学生提供兼职工作机会的平台,旨在搭建一个便捷、高效、安全的信息对接服务。该平台通过整合校内外各类兼职资源,包括但不限于家教、促销、实习等岗位,帮助学生找到适合自己的兼职工作,同时为用…

PostGIS 中的 K-Means 聚类操作及应用

K-Means算法: K-means 是数据科学和商业的基本算法。让我们深入了解一下。 1. K-means是一种流行的用于聚类的无监督机器学习算法。它是用于客户细分、库存分类、市场细分甚至异常检测的核心算法。 2. 无监督:K-means 是一种无监督算法,用于…

leetcode刷题(javaScript)——分治思想(二分查找、快速排序)相关场景题总结

分治思想是一种将问题分解成更小的子问题,然后解决子问题并将结果合并的算法设计策略。二分查找、快速排序和折半查找都属于分治思想的经典算法。在leetcode里,分治思想一般结合其他场景出现,构成复合型题目。但是在看题时一定要了解能否用分…