linux ksm,内存页面共享-KSM

article/2025/9/17 4:30:06

导读

本文适合有基本Linux内存管理概念的新手阅读,且本文旨在从工作流程和设计思想上介绍KSM,在涉及到源代码的地方,进行了部分删减,如果想详细了解KSM,推荐阅读源代码及源代码中的注释。

作者也是初次接触Linux内核源码,所以文章中难免出现纰漏,欢迎在评论中纠正。

一、KSM概述

KSM的全称是 Kernel Samepage Merging,主要应用在虚拟化环境中,它允许内核通过合并内存页面来节省内存,从来可以增加虚拟机的并发数据。

KSM核心设计思想是基于写时复制机制COW,也就是将内容相同的页面合并成一个只读页面,从而释放出空闲物理页面。

二、核心数据结构

KSM的核心数据结构有三个:

struct rmap_item

struct mm_slot

struct ksm_scan

rmap_item

从虚拟地址到一个 mm 的反向映射

同时,对于 stable tree,它负责组织其结构;对于 unstable tree,保存其校验和

struct rmap_item {

struct rmap_item *rmap_list; // 所有rmap_item连成一个链表,ksm_scam.rmap_list

union {

struct anon_vma *anon_vma; // rmap_item在stable树中时,指向VMA

#ifdef CONFIG_NUMA

int nid; // when node of unstable tree

#endif

};

struct mm_struct *mm; // 进程的struct mm_struct数据结构

unsigned long address; // rmap_item所跟踪的用户地址空间

unsigned int oldchecksum; // 虚拟地址对应的物理页面的旧校验值

union {

struct rb_node node; // rmap_item加入unstable红黑树的节点

struct { /* 在 stable tree 中时才有效 */

struct stable_node *head; // 加入stable红黑树的节点

struct hlist_node hlist; // stable链表

};

};

};

struct mm_slot

描述添加到KSM系统中将来要被扫描的进程mm_struct数据结构

struct mm_slot {

struct hlist_node link; // 用于添加到mm_slot哈希表中

struct list_head mm_list; // 用于添加到mm_slot链表中,链表头在ksm_mm_head

struct rmap_item *rmap_list; // rmap_item链表头

struct mm_struct *mm; // 进程的mm_sturct数据结构

};

struct ksm_scan

当前的扫描状态

struct ksm_scan {

struct mm_slot *mm_slot; // 当前正在扫描的mm_slot

unsigned long address; // 将要扫描的地址(page的地址)

struct rmap_item **rmap_list; // 将要扫描的rmap_item的指针

unsigned long seqnr; // 全部扫描完成后会计数一次,用于删除unstable节点

};

三、工作流程

8ca2375c1de7

image

上层的应用通过 madvise() 给某内存区域增加 MADV_MERGEABLE 或者 MADV_UNMERGEABLE 标记,造成对系统调用的访问,该系统调用由 SYSCALL_DEFINE3(madvise, unsigned long, start, size_t, len_in, int, behavior) 定义。

SYSCALL_DEFINE3

在这里会进行一个预处理,如找到该内存区域的所有VMA,并调用 madvise_vma 进行进一步处理。

SYSCALL_DEFINE3(madvise, unsigned long, start, size_t, len_in, int, behavior)

{

unsigned long end, tmp;

struct vm_area_struct *vma, *prev;

int unmapped_error = 0;

int error = -EINVAL;

int write;

size_t len;

struct blk_plug plug;

...

vma = find_vma_prev(current->mm, start, &prev);

if (vma && start > vma->vm_start)

prev = vma;

blk_start_plug(&plug);

for (;;) { // 在这个循环中遍历所有的VMA

error = -ENOMEM;

if (!vma)

goto out;

/* Here start < (end|vma->vm_end). */

if (start < vma->vm_start) {

unmapped_error = -ENOMEM;

start = vma->vm_start;

if (start >= end)

goto out;

}

// 获得VMA的终止地址

tmp = vma->vm_end;

if (end < tmp)

tmp = end;

// 将当前VMA的起始地址(start和end)传递给 madvise_vma,当然继承的标记behavior不能忘记

error = madvise_vma(vma, &prev, start, tmp, behavior);

if (error)

goto out;

start = tmp;

if (prev && start < prev->vm_end)

start = prev->vm_end;

error = unmapped_error;

if (start >= end)

goto out; // 这里是正常结束的出口

if (prev)

vma = prev->vm_next;

else

vma = find_vma(current->mm, start);

}

out:

blk_finish_plug(&plug);

if (write)

up_write(&current->mm->mmap_sem);

else

up_read(&current->mm->mmap_sem);

return error;

}

madvise_behavior

如果在某个VMA上发现 MADV_MERGEABLE 或者 MADV_UNMERGEABLE 标记,则触发 ksm_madvise

static long madvise_behavior(struct vm_area_struct *vma,

struct vm_area_struct **prev,

unsigned long start, unsigned long end, int behavior)

{

struct mm_struct *mm = vma->vm_mm;

int error = 0;

pgoff_t pgoff;

unsigned long new_flags = vma->vm_flags;

switch (behavior) {

...

case MADV_MERGEABLE:

case MADV_UNMERGEABLE:

error = ksm_madvise(vma, start, end, behavior, &new_flags);

if (error) {

/*

* madvise() returns EAGAIN if kernel resources, such as

* slab, are temporarily unavailable.

*/

if (error == -ENOMEM)

error = -EAGAIN;

goto out;

}

break;

case MADV_HUGEPAGE:

case MADV_NOHUGEPAGE:

error = hugepage_madvise(vma, &new_flags, behavior);

if (error) {

/*

* madvise() returns EAGAIN if kernel resources, such as

* slab, are temporarily unavailable.

*/

if (error == -ENOMEM)

error = -EAGAIN;

goto out;

}

break;

}

...

success:

vma->vm_flags = new_flags;

out:

return error;

}

ksm_madvise

在这一步会找到 vma 所属进程(mm),并判断标记决定是否对页面进行共享

如果需要共享,调用 __ksm_enter()并传递当前 vma 所属的 mm 地址。

int ksm_madvise(struct vm_area_struct *vma, unsigned long start,

unsigned long end, int advice, unsigned long *vm_flags)

{

struct mm_struct *mm = vma->vm_mm;

int err;

switch (advice) {

case MADV_MERGEABLE:

...

if (!test_bit(MMF_VM_MERGEABLE, &mm->flags)) {

err = __ksm_enter(mm); // 这是真正的入口

if (err)

return err;

}

*vm_flags |= VM_MERGEABLE;

break;

case MADV_UNMERGEABLE:

if (!(*vm_flags & VM_MERGEABLE))

return 0; /* just ignore the advice */

if (vma->anon_vma) {

err = unmerge_ksm_pages(vma, start, end);

if (err)

return err;

}

*vm_flags &= ~VM_MERGEABLE;

break;

}

return 0;

}

__ksm_enter

将进程的 mm 加入到 ksm_mm_head 链表中

int __ksm_enter(struct mm_struct *mm)

{

struct mm_slot *mm_slot;

int needs_wakeup;

mm_slot = alloc_mm_slot(); // 分配一个mm_slot,表示当前进程mm_struct数据结构

if (!mm_slot)

return -ENOMEM;

/* Check ksm_run too? Would need tighter locking */

needs_wakeup = list_empty(&ksm_mm_head.mm_list); // 为空表示当前没有正在被扫描的mm_slot

spin_lock(&ksm_mmlist_lock);

insert_to_mm_slots_hash(mm, mm_slot); // 将当前进程mm赋给mm_slot->mm

/*

* When KSM_RUN_MERGE (or KSM_RUN_STOP),

* insert just behind the scanning cursor, to let the area settle

* down a little; when fork is followed by immediate exec, we don't

* want ksmd to waste time setting up and tearing down an rmap_list.

*

* But when KSM_RUN_UNMERGE, it's important to insert ahead of its

* scanning cursor, otherwise KSM pages in newly forked mms will be

* missed: then we might as well insert at the end of the list.

*/

if (ksm_run & KSM_RUN_UNMERGE)

list_add_tail(&mm_slot->mm_list, &ksm_mm_head.mm_list);

else

list_add_tail(&mm_slot->mm_list, &ksm_scan.mm_slot->mm_list); // mm_slot添加到ksm_scan.mm_slot->mm_list链表中

spin_unlock(&ksm_mmlist_lock);

set_bit(MMF_VM_MERGEABLE, &mm->flags); // 表示这个进程已经添加到KSM系统中

mmgrab(mm);

if (needs_wakeup)

wake_up_interruptible(&ksm_thread_wait); // 则唤醒ksmd内核线程

return 0;

}

ksm_init

创建内核线程ksmd

static int __init ksm_init(void)

{

struct task_struct *ksm_thread;

int err;

/* The correct value depends on page size and endianness */

zero_checksum = calc_checksum(ZERO_PAGE(0));

/* Default to false for backwards compatibility */

ksm_use_zero_pages = false;

err = ksm_slab_init(); // 创建ksm_rmap_item、ksm_stable_node、ksm_mm_slot三个高速缓存

if (err)

goto out;

ksm_thread = kthread_run(ksm_scan_thread, NULL, "ksmd"); // 创建ksmd内核线程,线程函数为ksm_scan_thread

if (IS_ERR(ksm_thread)) {

pr_err("ksm: creating kthread failed\n");

err = PTR_ERR(ksm_thread);

goto out_free;

}

#ifdef CONFIG_SYSFS

err = sysfs_create_group(mm_kobj, &ksm_attr_group); // 在sys/kernel/mm下创建ksm相关节点

if (err) {

pr_err("ksm: register sysfs failed\n");

kthread_stop(ksm_thread);

goto out_free;

}

#else

ksm_run = KSM_RUN_MERGE; /* no way for user to start it */

#endif /* CONFIG_SYSFS */

#ifdef CONFIG_MEMORY_HOTREMOVE

/* There is no significance to this priority 100 */

hotplug_memory_notifier(ksm_memory_callback, 100);

#endif

return 0;

out_free:

ksm_slab_free();

out:

return err;

}

ksm_scan_thread

只需要知道这里进入ksm_do_scan()进行实际的操作就可以了

static int ksm_scan_thread(void *nothing)

{

set_freezable();

set_user_nice(current, 5);

while (!kthread_should_stop()) {

mutex_lock(&ksm_thread_mutex);

wait_while_offlining();

if (ksmd_should_run())

ksm_do_scan(ksm_thread_pages_to_scan); // 实际的执行者,参数是一次扫描的页面数量

mutex_unlock(&ksm_thread_mutex);

try_to_freeze();

if (ksmd_should_run()) {

schedule_timeout_interruptible(

msecs_to_jiffies(ksm_thread_sleep_millisecs));

} else {

wait_event_freezable(ksm_thread_wait,

ksmd_should_run() || kthread_should_stop());

}

}

return 0;

}

ksm_do_scam

static void ksm_do_scan(unsigned int scan_npages)

{

struct rmap_item *rmap_item;

struct page *uninitialized_var(page);

while (scan_npages-- && likely(!freezing(current))) { // 一次扫描 scan_npages 个页面

cond_resched();

rmap_item = scan_get_next_rmap_item(&page); // 找到一个合适的匿名page,并为其建立由物理地址到虚拟地址的反向映射

if (!rmap_item)

return;

cmp_and_merge_page(page, rmap_item); // 查找两棵树看看是不是能合并这个page

put_page(page);

}

}

cmp_and_merge_page

首先查看这个 page 是不是可以和 stable tree 中的 page 合并,不可以的话,检查下它的检验值和之前是否有变化,如果变化则认为是写频繁的页面,跳过它;否则,决定是将它插入到 unstable tree 中,或与其中节点合并加入到 stable tree 中。

static void cmp_and_merge_page(struct page *page, struct rmap_item *rmap_item)

{

struct rmap_item *tree_rmap_item;

struct page *tree_page = NULL;

struct stable_node *stable_node;

struct page *kpage;

unsigned int checksum;

int err;

stable_node = page_stable_node(page);

if (stable_node) {

if (stable_node->head != &migrate_nodes &&

get_kpfn_nid(stable_node->kpfn) != NUMA(stable_node->nid)) {

rb_erase(&stable_node->node,

root_stable_tree + NUMA(stable_node->nid));

stable_node->head = &migrate_nodes;

list_add(&stable_node->list, stable_node->head);

}

if (stable_node->head != &migrate_nodes &&

rmap_item->head == stable_node)

return;

}

kpage = stable_tree_search(page); // 查 stable tree

if (kpage == page && rmap_item->head == stable_node) { // 查找结果就是自身的情况

put_page(kpage);

return;

}

remove_rmap_item_from_tree(rmap_item); // 为什么???????????

if (kpage) { // kpage 和 page 内容相同且不是同一页面

err = try_to_merge_with_ksm_page(rmap_item, page, kpage); // 尝试合并页面

if (!err) {

lock_page(kpage);

stable_tree_append(rmap_item, page_stable_node(kpage)); // 合并成功,把rmap_item添加到stable_node->hlist哈希链表上

unlock_page(kpage);

}

put_page(kpage);

return;

}

/********************************下面是 unstable tree 的处理 ***********************/

// 计算检验值,如果与旧值不等,说明页面写频繁,不适合KSM

checksum = calc_checksum(page);

if (rmap_item->oldchecksum != checksum) {

rmap_item->oldchecksum = checksum;

return;

}

/*

* Same checksum as an empty page. We attempt to merge it with the

* appropriate zero page if the user enabled this via sysfs.

*/

if (ksm_use_zero_pages && (checksum == zero_checksum)) {

struct vm_area_struct *vma;

vma = find_mergeable_vma(rmap_item->mm, rmap_item->address);

err = try_to_merge_one_page(vma, page,

ZERO_PAGE(rmap_item->address));

/*

* In case of failure, the page was not really empty, so we

* need to continue. Otherwise we're done.

*/

if (!err)

return;

}

// 搜索 unstable tree 中查看是否有相同页面

tree_rmap_item =

unstable_tree_search_insert(rmap_item, page, &tree_page);

if (tree_rmap_item) {

// 尝试将 page 和 tree_page 合并成一个 kpage

kpage = try_to_merge_two_pages(rmap_item, page,

tree_rmap_item, tree_page);

put_page(tree_page);

if (kpage) {

/*

* 合并成功,将 kpage 插入到 stable tree 中和 rmap_items 中

*/

lock_page(kpage);

stable_node = stable_tree_insert(kpage);

if (stable_node) {

stable_tree_append(tree_rmap_item, stable_node);

stable_tree_append(rmap_item, stable_node);

}

unlock_page(kpage);

/*

* 如果stable_node插入到stable树失败,

* 那么调用break_cow()主动触发一个缺页中断

* 来分离这个KSM页面

*/

if (!stable_node) {

break_cow(tree_rmap_item);

break_cow(rmap_item);

}

}

}

}

try_to_merge_one_page

将两个 page 进行合并

/*

* try_to_merge_one_page - take two pages and merge them into one

* @vma: the vma that holds the pte pointing to page

* @page: the PageAnon page that we want to replace with kpage

* @kpage: the PageKsm page that we want to map instead of page,

* or NULL the first time when we want to use page as kpage.

*/

static int try_to_merge_one_page(struct vm_area_struct *vma,

struct page *page, struct page *kpage)

{

pte_t orig_pte = __pte(0);

int err = -EFAULT;

if (page == kpage) /* ksm page forked */

return 0;

if (!PageAnon(page))

goto out;

/*

* We need the page lock to read a stable PageSwapCache in

* write_protect_page().

*/

if (!trylock_page(page)) // 我们不希望在此等待,所以不直接 lock_page

goto out; // 跳过这个 page 转而去处理其他 page

if (PageTransCompound(page)) { // 如果 page 是透明大页,返回 1

if (split_huge_page(page)) // 将大页分裂成 normal pages,成功则返回 0

goto out_unlock;

}

/*

* If this anonymous page is mapped only here, its pte may need

* to be write-protected. If it's mapped elsewhere, all of its

* ptes are necessarily already write-protected. But in either

* case, we need to lock and check page_count is not raised.

*/

if (write_protect_page(vma, page, &orig_pte) == 0) {

if (!kpage) {

/*

* While we hold page lock, upgrade page from

* PageAnon+anon_vma to PageKsm+NULL stable_node:

* stable_tree_insert() will update stable_node.

*/

set_page_stable_node(page, NULL);

mark_page_accessed(page);

/*

* Page reclaim just frees a clean page with no dirty

* ptes: make sure that the ksm page would be swapped.

*/

if (!PageDirty(page))

SetPageDirty(page);

err = 0;

} else if (pages_identical(page, kpage))

err = replace_page(vma, page, kpage, orig_pte); //

}

if ((vma->vm_flags & VM_LOCKED) && kpage && !err) {

munlock_vma_page(page);

if (!PageMlocked(kpage)) {

unlock_page(page);

lock_page(kpage);

mlock_vma_page(kpage);

page = kpage; /* for final unlock */

}

}

out_unlock:

unlock_page(page);

out:

return err;

}

附:

scan_get_next_rmap_item()

遍历ksm_mm_head链表(即进程的mm),然后再遍历进程地址空间的每个VMA,然后通过get_next_rmpa_item()返回rmap_item

static struct rmap_item *scan_get_next_rmap_item(struct page **page)

{

struct mm_struct *mm;

struct mm_slot *slot;

struct vm_area_struct *vma;

struct rmap_item *rmap_item;

int nid;

if (list_empty(&ksm_mm_head.mm_list)) // 没有mm_slot,直接退出

return NULL;

slot = ksm_scan.mm_slot;

if (slot == &ksm_mm_head) { // 第一次运行ksmd,进行初始化

...

}

mm = slot->mm;

down_read(&mm->mmap_sem);

if (ksm_test_exit(mm))

vma = NULL;

else

vma = find_vma(mm, ksm_scan.address); // 根据 address 在 mm 中找到 vma

for (; vma; vma = vma->vm_next) { // 遍历当前mm的所有VMA

if (!(vma->vm_flags & VM_MERGEABLE))

continue;

if (ksm_scan.address < vma->vm_start)

ksm_scan.address = vma->vm_start;

if (!vma->anon_vma)

ksm_scan.address = vma->vm_end;

while (ksm_scan.address < vma->vm_end) { // 遍历VMA中的所有页面

if (ksm_test_exit(mm))

break;

*page = follow_page(vma, ksm_scan.address, FOLL_GET); // 根据虚拟地址找到物理页

if (IS_ERR_OR_NULL(*page)) {

ksm_scan.address += PAGE_SIZE;

cond_resched();

continue;

}

if (PageAnon(*page)) { // 只考虑匿名页面

flush_anon_page(vma, *page, ksm_scan.address);

flush_dcache_page(*page);

rmap_item = get_next_rmap_item(slot,

ksm_scan.rmap_list, ksm_scan.address); // 去找mm_slot->rmap_list链表上是否有该虚拟地址对应的rmap_item,没有就新建一个

if (rmap_item) {

ksm_scan.rmap_list =

&rmap_item->rmap_list;

ksm_scan.address += PAGE_SIZE;

} else

put_page(*page);

up_read(&mm->mmap_sem);

return rmap_item;

}

put_page(*page);

ksm_scan.address += PAGE_SIZE;

cond_resched();

}

}

if (ksm_test_exit(mm)) { // for循环里扫描该进程所有的VMA都没找到合适的匿名页面

ksm_scan.address = 0;

ksm_scan.rmap_list = &slot->rmap_list;

}

/*

* Nuke all the rmap_items that are above this current rmap:

* because there were no VM_MERGEABLE vmas with such addresses.

*/

remove_trailing_rmap_items(slot, ksm_scan.rmap_list); // 在该进程中没找到合适的匿名页面时,那么对应的rmap_item已经没用必要占用空间,直接删除

spin_lock(&ksm_mmlist_lock);

ksm_scan.mm_slot = list_entry(slot->mm_list.next,

struct mm_slot, mm_list); // 取下一个mm_slot

if (ksm_scan.address == 0) { // 处理该进程被销毁的情况,把mm_slot从ksm_mm_head链表中删除,释放mm_slot数据结构,清MMF_VM_MERGEABLE标志位。

/*

* We've completed a full scan of all vmas, holding mmap_sem

* throughout, and found no VM_MERGEABLE: so do the same as

* __ksm_exit does to remove this mm from all our lists now.

* This applies either when cleaning up after __ksm_exit

* (but beware: we can reach here even before __ksm_exit),

* or when all VM_MERGEABLE areas have been unmapped (and

* mmap_sem then protects against race with MADV_MERGEABLE).

*/

hash_del(&slot->link);

list_del(&slot->mm_list);

spin_unlock(&ksm_mmlist_lock);

free_mm_slot(slot);

clear_bit(MMF_VM_MERGEABLE, &mm->flags);

up_read(&mm->mmap_sem);

mmdrop(mm);

} else {

up_read(&mm->mmap_sem);

/*

* up_read(&mm->mmap_sem) first because after

* spin_unlock(&ksm_mmlist_lock) run, the "mm" may

* already have been freed under us by __ksm_exit()

* because the "mm_slot" is still hashed and

* ksm_scan.mm_slot doesn't point to it anymore.

*/

spin_unlock(&ksm_mmlist_lock);

}

/* Repeat until we've completed scanning the whole list */

slot = ksm_scan.mm_slot;

if (slot != &ksm_mm_head)

goto next_mm; // 继续扫描下一个mm_slot

ksm_scan.seqnr++; // 扫描完一轮mm_slot,增加计数

return NULL;

}


http://chatgpt.dhexx.cn/article/qIJ2Bvj9.shtml

相关文章

Linux内存管理 之 KSM功能介绍

目录 前言1. 概述1.1 使用 madvise 控制 KSM1.1 KSM 守护进程 sysfs 接口 2.KSM技术应用场景3. KSM技术实践3.1 查看KSM运行情况3.2 KSM设置3.3 对vm的设置 前言 KSM(Kernel Samepage Merging) 共享内存的概念在现代操作系统中很常用了&#xff0c;比如&#xff0c;一个程序启…

HTTP 401 错误 - 未授权: (Unauthorized)

http://blog.csdn.net/patronsaint/article/details/5639962 介绍 您的Web服务器认为&#xff0c;客户端&#xff08;例如您的浏览器或我们的 CheckUpDown 机器人&#xff09;发送的 HTTP 数据流是正确的&#xff0c;但进入网址 (URL) 资源 &#xff0c; 需要用户身份验证 &a…

403错误(已解决)

问题描述&#xff1a; 取得到图片的地址&#xff0c;但是显示图片无法加载 浏览器开发者模式报错403 问题解决&#xff1a; 图片的url部署在阿里云的oss存储仓库上&#xff0c;登陆阿里云发现是欠费导致的oss文件存储功能失效 补充&#xff1a; 403 Forbidden错误的原因和解…

Error:Request failed with status code 401

Error:Request failed with status code 401 错误示例&#xff1a; // 获取个人信息 export const getUserInfoAPI () > {return request({url: /v1_0/user/profile,method: GET,Headers: {Authorization: Bearer ${getToken()}}}) }正确做法 // 获取个人信息 export co…

服务器发送了 HTTP 状态代码 401: Unauthorized

根据wsdl连接生成客户端代码&#xff0c;调用接口报错&#xff1a;服务器发送了 HTTP 状态代码 401: Unauthorized。查询此错误的原因是没有访问权限&#xff0c;想到客户再提供接口wsdl访问连接的时候提供了用户名和密码。 解决办法&#xff1a; 增加此类 在接口运行前增加此…

前端401错误 解决方法:响应拦截器

目录 1.该问题出现的原因 2.处理401问题的解决方案原理 3.使用响应拦截器解决问题 1.该问题出现的原因 在前后端分离项目中&#xff0c;最常见的是前端点击登录后&#xff0c;后端返回token字符串&#xff0c;这个token可以看作是一个“令牌”&#xff0c;就比如你去酒店办理…

解密网站401错误:了解发生原因和修复方法

​  每个网站都会有不同的错误码&#xff0c;其中&#xff0c;401错误被认为是相对常见的错误码。那么&#xff0c;什么是网站401错误呢?在摸清了这一点之后&#xff0c;我们也需要学习一下如何解决它。 什么是 401 状态码? 401 状态代码是 Web 服务器发送给浏览器的 HTTP …

401 错误原因和解决方案

401.1 您未被授权查看该页 HTTP 错误 401.1 - 未经授权&#xff1a;访问由于 凭据无效被拒绝。 出现问题的原因&#xff1a; 计算机用户名ftpname帐号的密码和IIS中记录的不一致。 出现这个问题的原因就是手工到IIS中修改了用户的登录密码&#xff0c;从而导致计算机用户名…

401错误 解决方法:响应拦截器

关于401错误 401错误场景 1. 用户未登录,代码报401,应该回到登录页 2. 登录用户的token过期 : 怎样理解token过期? . 就是登录成功了以后,后端会返回一个token值,这个值在后续请求时带上&#xff08;就像是开门钥匙), 但是&#xff0c;这个值一般会有有效期&#xff08;具…

点到超平面的距离公式

超平面&#xff1a; 第一步&#xff1a;求出超平面的法向量 超平面上任意两点, 则向量一定垂直于该超平面的法向量。这两点满足&#xff1a; 两式相减&#xff1a; 就是向量&#xff0c;由此可以看出超平面的法向量就是向量 第二步&#xff1a;求出点到超平面的距离r 假设…

模式识别中的最优分类超平面与线性支持向量机

模式识别中的最优分类超平面与线性支持向量机 本章的内容需要你理解一下的基础知识。 模式识别部分&#xff1a;线性分类器基本概念、感知器 数学基础部分&#xff1a;拉格朗日乘子法、拉格朗日对偶问题 当然&#xff0c;以上的基础知识不需要你现在就看&#xff0c;可以在阅…

平面、超平面的法线,平行超平面的距离

1. 法线 一个平面或超平面由一个方程&#xff08;不是一组方程&#xff09;定义&#xff1a; { x ∣ a T x b } \{x\mid a^Txb\} {x∣aTxb} 其中 a a a 是一个非零的列向量&#xff0c;而 b b b 是一个实数。那么它的法线&#xff08;垂线&#xff09;向量是 a a a。 例如…

如何理解超平面?

超平面的公式 首先明确几个定义&#xff1a;(1) 超平面是指n维线性空间中维度为n-1的子空间。它可以把线性空间分割成不相交的两部分。比如二维空间中&#xff0c;一条直线是一维的&#xff0c;它把平面分成了两块&#xff1b;三维空间中&#xff0c;一个平面是二维的&#xf…

支持向量所在超平面方程_如何用python绘制超平面支持向量机?

一个完整的例子 import numpy as np import matplotlib.pyplot as plt def __intersect(rect, line): l = [] xmin,xmax,ymin,ymax = rect a,b,c = line assert a!=0 or b!=0 if a == 0: y = -c/b if y<=ymax and y>=ymin: l.append((xmin, y)) l.append((xmax, y)) retu…

SVM(四):超平面详细解释

目录 背景定义超平面方程推导平面直线方程空间平面方程超平面 点到超平面的距离推导点到平面直线的距离点到空间平面的距离超平面 判断超平面的正反 背景 关于超平面的介绍&#xff0c;网上的博客资料太多了&#xff0c;然而真正简洁易懂、切中要害的实在是太少了&#xff0c;…

【支持向量机】最大间隔超平面及Matlab代码

线性可分 在特征空间中&#xff0c;有两个训练样本可以通过一条直线区分开&#xff0c;则称为线性可分。而在特征空间大于等于四维时&#xff0c;分开训练样本的平面&#xff0c;称为超平面。 我们定义一条直线方程&#xff1a; ω 1 x 1 ω 2 x 2 b 0 ω_1x_1ω_2x_2b0 …

第三节- 超平面

这一节我们来讲一个新的东西&#xff0c;叫做 超 平面( hyper-plane)。 其实这个东西本身并不难理解。 只是我们在三维直角坐标系里面习惯了 直线 平面什么的&#xff0c;可能有些疑惑而已。 老规矩&#xff0c;我们先甩出定义&#xff1a; 超平面 hyperplane 看起来&am…

躺平国历险记:两个超平面之间距离的计算

王二麻听说躺平国是一个非常神奇的地方。在躺平国里&#xff0c;有各种神奇的东西。比如&#xff1a;会说话的铁片&#xff0c;能够分拣垃圾的小狗&#xff0c;还有会唱京剧的电线杆子。 王二麻的表哥张三李是一个糙汉子&#xff0c;他前几年在国内混不下去了&#xff0c;一个…

SVM超平面的理解

超平面的理解 超平面的定义 超平面是n维欧氏空间中(n-1)维度的子空间&#xff0c;例如3维空间的“超平面”就是一个面&#xff0c;但通常超过3维空间之后我们才称之为超平面&#xff0c;是纯粹的数学概念&#xff0c;不是现实的物理概念。公式表示为1-1&#xff0c;W、x是一个…

SVM原理:超平面方程

&#xff08;1&#xff09;超平面方程 3维空间中平面方程的一般形式&#xff1a; (1) 我们都知道为平面到原点的距离。这里简单证明超平面的法向量为。 d维空间平面方程的一般形式&#xff1a; (2) 平面的法向量为&#xff0c;&#xff08;分号表示列向量&#xff09;。 &…