iomemory地址被抢占(一)

本文详细描述了内核中内存分配冲突的原因及解决方法,通过调整内存资源的顺序,避免了冲突的发生。同时,深入分析了request_mem_region函数的实现过程,包括内存资源的创建、冲突检测和内存释放等关键步骤。

最近做的一个项目,系统启动时发现有warning,并打印出了一堆dump stack。
看了下代码,发现是在esai的probe函数中调用request_mem_region时失败代码走到了错误处理的部分。
错误处理中disable了一个clock,warning的内容是说该clock没enable就disable了。

从代码看,存在两个问题:
1、request_mem_region为什么会失败。
2、为什么在disable clock之前没有enable。

先看看request_mem_region为什么会失败。
经大牛指点,request_mem_region失败,应该是因为申请的一块内存,部分/全部已被占用,所以导致申请失败。
为了看看申请的内存是否与其他io申请的内存释放有冲突,将/proc/iomem文件cat出来,发现esai申请的内存与ssi-0申请的内存重叠。
对照cpu的reference manual,发现esai很守规矩,使用的是memory map中给自己指定的memory范围;而ssi-0没使用memory map中指定的内存范围,抢占了esai的内存范围。
找到esai base address和ssi-0(对应的是硬件ssi-1) base address定义,发现定义没问题,与memory map中一致。
在esai和ssi的probe函数中加log,发现首先是ssi-1申请了正确的内存,ssi-2也申请了正确的内存,接下来ssi-0申请的内存范围是esai的内存范围。
因为ssi在esai前面进行的probe,导致esai申请内存时,内存已经被ssi-0申请了去,所以失败。
可疑点是,ssi的顺序为什么是1、2、0,而不是0、1、2。
看了board文件中添加resource的地方,发现是先添加了ssi 1、2、3,然后添加了esai。
跟了下添加ssi resource的代码,发现知道的序号其实用作了数组下标,数组下标都是从0开始的,这儿为什么是1、2、3?
是不是添加ssi 1\2\3的时候,1、2没问题,由于ssi只有0\1\2,添加3的时候,抢占了esai的位置。
将board中的ssi 1\2\3改为0\1\2,果然问题解决。

若只是止步于此,少了些乐趣。
不仅要知其然,也要知其所以然。

先看看request_mem_region的实现。

#define request_mem_region(start, n, name) __request_region(&iomem_resource, (start), (n), (name), 0)


看看iomem_resource的定义:

struct resource iomem_resource = {
	.name	= "PCI mem",
	.start	= 0,
	.end	= -1,
	.flags	= IORESOURCE_MEM,
};


resource type有以下几种类型:

#define IORESOURCE_IO		0x00000100
#define IORESOURCE_MEM		0x00000200
#define IORESOURCE_IRQ		0x00000400
#define IORESOURCE_DMA		0x00000800
#define IORESOURCE_BUS		0x00001000


看看__request_region的实现:

/**
 * __request_region - create a new busy resource region
 * @parent: parent resource descriptor
 * @start: resource start address
 * @n: resource region size
 * @name: reserving caller's ID string
 * @flags: IO resource flags
 */
struct resource * __request_region(struct resource *parent,
				   resource_size_t start, resource_size_t n,
				   const char *name, int flags)
{
	// 定义并初始化一个wait queue
	/*
	#define DECLARE_WAITQUEUE(name, tsk)					\
	wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
	*/
	/*
	#define __WAITQUEUE_INITIALIZER(name, tsk) {				\
	.private	= tsk,						\
	.func		= default_wake_function,			\
	.task_list	= { NULL, NULL } }
	*/
	DECLARE_WAITQUEUE(wait, current);
	
	/*
/*
 * Resources are tree-like, allowing
 * nesting etc..
 */
struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};
	*/
	/*
/**
 * kzalloc - allocate memory. The memory is set to zero.
 * @size: how many bytes of memory are required.
 * @flags: the type of memory to allocate (see kmalloc).
 */
static inline void *kzalloc(size_t size, gfp_t flags)
{
	return kmalloc(size, flags | __GFP_ZERO);
}
	*/
	struct resource *res = kzalloc(sizeof(*res), GFP_KERNEL);

	if (!res)
		return NULL;

	res->name = name;
	res->start = start;
	res->end = start + n - 1;
	res->flags = IORESOURCE_BUSY;
	res->flags |= flags;

	write_lock(&resource_lock);

	for (;;) {
		struct resource *conflict;

		// 检查申请的内存与其他内存是否冲突
		/×
/* Return the conflict entry if you can't request it */
static struct resource * __request_resource(struct resource *root, struct resource *new)
{
	resource_size_t start = new->start;
	resource_size_t end = new->end;
	struct resource *tmp, **p;

	// 头比尾大,自相矛盾
	if (end < start)
		return root;
	// 头还在根的头前面,越界了
	if (start < root->start)
		return root;
	// 尾在根的尾后面,也越界了
	if (end > root->end)
		return root;
	// 从下面的循环看,root的所有child应该是从低地址到高地址依次排开,并通过他们的sibling进行关联。
	// 刚开始,p指向root的最大孩子,也就是地址最低的,并判断其与new是否冲突。
	// 若不冲突,通过最大孩子的sibling成员,找到它的下一个兄弟,并判断其与new是否冲突。
	// 这样依次判断下去,直到找到了一个空child,或者找到的找到的child的start还在new的end之后,也即完全在new之后。
	p = &root->child;
	for (;;) {
		tmp = *p;
		if (!tmp || tmp->start > end) {
			new->sibling = tmp;
			*p = new;
			new->parent = root;
			return NULL;
		}
		p = &tmp->sibling;
		if (tmp->end < start)
			continue;
		return tmp;
	}
}
		×/
		conflict = __request_resource(parent, res);
		if (!conflict)
			break;
		// 若有冲突,但冲突的不是根,并且conflict的标志并没有说是busy,也就是说并没有被占用,则说明new落在了原来根的一个child的范围内。
		// 也就是说conflict其实是new的根,那就将conflict赋值为new的根,再次进行冲突检测。
		if (conflict != parent) {
			parent = conflict;
			if (!(conflict->flags & IORESOURCE_BUSY))
				continue;
		}
		// 如果有冲突,并且conflict和new都是可软件多路复用的,则说明暂时是被别人占用了,
		// 要做到就是把锁释放了,并等待内存被释放
		if (conflict->flags & flags & IORESOURCE_MUXED) {
			/*
/*
 * This is compatibility stuff for IO resources.
 *
 * Note how this, unlike the above, knows about
 * the IO flag meanings (busy etc).
 *
 * request_region creates a new busy region.
 *
 * check_region returns non-zero if the area is already busy.
 *
 * release_region releases a matching busy region.
 */

static DECLARE_WAIT_QUEUE_HEAD(muxed_resource_wait);

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
	unsigned long flags;

	wait->flags &= ~WQ_FLAG_EXCLUSIVE;
	spin_lock_irqsave(&q->lock, flags);
	__add_wait_queue(q, wait);
	spin_unlock_irqrestore(&q->lock, flags);
}

static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
	list_add(&new->task_list, &head->task_list);
}
			*/
			add_wait_queue(&muxed_resource_wait, &wait);
			// 释放锁
			write_unlock(&resource_lock);
			// TASK_INTERRUPTIBLE是可以被信号和wake_up()唤醒的,当信号到来时,进程会被设置为可运行。
			// TASK_UNINTERRUPTIBLE只能被wake_up()唤醒。
			set_current_state(TASK_UNINTERRUPTIBLE);
			/*
asmlinkage void __sched schedule(void)
{
	struct task_struct *tsk = current;

	sched_submit_work(tsk);
	__schedule();
}

static inline void sched_submit_work(struct task_struct *tsk)
{
	if (!tsk->state)
		return;
	/*
	 * If we are going to sleep and we have plugged IO queued,
	 * make sure to submit it to avoid deadlocks.
	 */
	if (blk_needs_flush_plug(tsk))
		blk_schedule_flush_plug(tsk);
}
			*/
			// 调度函数,不能简单的一笔带过
			schedule();
			remove_wait_queue(&muxed_resource_wait, &wait);
			write_lock(&resource_lock);
			continue;
		}
		/* Uhhuh, that didn't work out.. */
		kfree(res);
		res = NULL;
		break;
	}
	write_unlock(&resource_lock);
	return res;
}


上面有将wait添加到queue,在__release_region中调用了wake_up用来唤醒等待的进程。

/**
 * __release_region - release a previously reserved resource region
 * @parent: parent resource descriptor
 * @start: resource start address
 * @n: resource region size
 *
 * The described resource region must match a currently busy region.
 */
void __release_region(struct resource *parent, resource_size_t start,
			resource_size_t n)
{
	struct resource **p;
	resource_size_t end;

	p = &parent->child;
	end = start + n - 1;

	write_lock(&resource_lock);

	for (;;) {
		struct resource *res = *p;

		if (!res)
			break;
		if (res->start <= start && res->end >= end) {
			if (!(res->flags & IORESOURCE_BUSY)) {
				p = &res->child;
				continue;
			}
			if (res->start != start || res->end != end)
				break;
			*p = res->sibling;
			write_unlock(&resource_lock);
			if (res->flags & IORESOURCE_MUXED)
				wake_up(&muxed_resource_wait);
			kfree(res);
			return;
		}
		p = &res->sibling;
	}

	write_unlock(&resource_lock);

	printk(KERN_WARNING "Trying to free nonexistent resource "
		"<%016llx-%016llx>\n", (unsigned long long)start,
		(unsigned long long)end);
}


<think>好的,我现在需要回答用户的问题:如何在Kubernetes中配置个服务器仅运行个Pod。首先,我需要理解用户的需求。用户希望某个特定的服务器(节点)上只能运行个Pod,这可能是因为资源限制、许可证问题,或者需要专用节点的情况。接下来,我要回忆Kubernetes的相关知识,并结合提供的引用内容来找到解决方法。 根据引用[2],提到Pod中的多个容器总是运行在同个工作节点上,不会跨节点。这说明Kubernetes默认情况下调度Pod到可用节点,但用户的需求是限制单个节点只能运行个Pod。这可能涉及到节点选择、污点(Taints)和容忍(Tolerations)、节点亲和性(Node Affinity)或者资源配额(Resource Quotas)等机制。 首先,想到的是节点选择器(nodeSelector),可以将Pod指定到特定节点,但这只能确保Pod在指定节点运行,而无法阻止其他Pod也被调度到该节点。因此需要更严格的控制。 接下来,污点和容忍可能更有效。给目标节点设置污点,然后Pod需要有对应的容忍才能被调度。但如果没有其他Pod容忍这个污点,那么只有特定的Pod能运行在该节点。但是用户希望该节点仅运行个Pod,所以可能需要结合其他机制。 另个方法是使用节点亲和性中的反亲和性(Anti-Affinity),确保同节点上没有其他Pod。例如,设置Pod的反亲和性规则,使得如果有其他Pod存在,则不会调度到同节点。但这种方法可能需要特定的标签来匹配,并且需要确保其他Pod不会带有相同的标签。 另外,资源配额可能也是种方式,如果节点资源被完全分配给个Pod,那么其他Pod无法调度,但需要精确计算资源需求,这可能不太灵活。 结合引用中的例子,比如静态Pod(引用[3]),静态Pod是由kubelet直接管理的,放置在特定目录下的YAML文件会被自动创建。如果将静态Pod配置到某个节点,并且该节点不被Kubernetes调度其他Pod,可能实现单Pod运行。但通常静态Pod和常规Pod可以共存,所以可能需要结合节点污点来阻止常规Pod的调度。 总结可能的步骤: 1. 标记目标节点,例如添加标签。 2. 为目标节点设置污点,阻止其他Pod调度。 3. 配置Pod使用节点选择器和容忍,使其只能在目标节点运行。 4. 确保没有其他Pod被调度到该节点,可能还需要资源限制或反亲和性。 例如,给节点打上专用标签,设置污点,Pod必须容忍该污点,并只能在该节点运行。同时,其他Pod如果没有对应的容忍,则不会被调度到该节点。这样就能保证该节点只运行这个Pod。 还需要检查是否有其他机制,如Pod优先级和抢占,但可能复杂度更高。根据引用[4]中的配置例子,可能需要在Pod的YAML中指定nodeSelector、tolerations,以及亲和性规则。 可能需要具体步骤: - 使用kubectl给节点打标签。 - 设置节点污点。 - 在Pod配置中添加对应的nodeSelector和tolerations。 - 使用反亲和性规则,确保只有个Pod。 或者,如果用户希望该节点仅运行个特定的Pod,而不管其他情况,可以考虑将该节点设置为不可调度(cordon),然后手动创建Pod在该节点,但这样可能不灵活。 或者,使用DaemonSet,但DaemonSet通常在每个节点运行个Pod实例,但用户可能只需要在特定节点运行个Pod,而其他节点不运行。这可能不符合需求。 现在需要验证这些思路是否正确。例如,污点和容忍可以阻止其他Pod调度,结合节点选择器,确保只有特定Pod能容忍污点并被调度到该节点,这样其他Pod无法在此运行。但如果有多个Pod配置了相同的容忍和选择器,它们还是可能被调度到同节点,所以需要确保只有个这样的Pod存在。这可能还需要资源限制,比如节点的资源只能满足个Pod的需求,或者使用反亲和性来阻止同节点上的多个实例。 例如,使用PodAntiAffinity,设置requiredDuringSchedulingIgnoredDuringExecution,这样调度器不会在同个节点上调度相同标签的Pod。这样结合节点选择器和污点,就能确保该节点只运行个Pod。 现在,综合这些方法,给出具体的步骤: 1. 标记目标节点,比如kubectl label nodes <node-name> dedicated=singleton 2. 设置污点:kubectl taint nodes <node-name> dedicated=singleton:NoSchedule 3. 创建Pod的配置,使用nodeSelector选择该节点,添加容忍该污点,并设置PodAntiAffinity防止同节点上有相同Pod。 但PodAntiAffinity可能需要定义拓扑域,比如针对节点的hostname,这样同拓扑域(即同节点)不会调度多个实例。 例如,在Pod的spec中添加: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - myapp topologyKey: kubernetes.io/hostname 这样,如果有其他Pod带有app=myapp标签,则不会被调度到同节点。 但用户的需求是节点上仅运行个Pod,无论该Pod是什么。所以可能需要更严格的反亲和性,但这可能不现实,因为无法预知所有可能的Pod标签。 另种思路是使用资源配额,将节点的CPU和内存完全分配给个Pod,这样其他Pod无法调度,因为资源不足。例如,计算节点总资源,设置Pod的requests等于节点资源,但需要精确计算,且节点可能有系统进程占用资源,这容易出错。 综合来看,最可靠的方法可能是结合节点污点和容忍,以及节点专用,同时确保只有个Pod被创建。例如: - 将节点标记为专用,添加污点,只有特定的Pod能容忍。 - 不创建其他Pod使用该容忍,这样该节点只能运行这个Pod。 但需要用户自觉不部署其他Pod到该节点,这可能不够可靠。 所以,更严谨的方法可能包括: 1. 将节点设置为不可调度(cordon),防止新Pod被调度。 2. 手动创建Pod在该节点,但这样无法利用调度器,可能不推荐。 3. 使用静态Pod(如引用[3]),将Pod定义放在节点的静态Pod目录下,这样kubelet会管理它,并且不会通过API服务器调度其他Pod到该节点,但需要确认静态Pod是否会影响调度。 不过,静态Pod通常和常规Pod共存,所以可能还需要结合其他机制。 最终,可能的最佳实践是: - 使用节点亲和性将Pod绑定到特定节点。 - 使用污点阻止其他Pod调度。 - 结合资源配额或反亲和性确保只有个Pod存在。 因此,具体步骤可能包括: 1. 标记目标节点: kubectl label node <node-name> dedicated=singleton 2. 给节点添加污点: kubectl taint node <node-name> dedicated=singleton:NoSchedule 3. 创建Pod时,配置nodeSelector选择该标签,并添加对应的容忍: tolerations: - key: dedicated operator: Equal value: singleton effect: NoSchedule 4. 同时,在Pod中设置资源requests为节点的全部可用资源,这样其他Pod无法调度,因为资源不足。但需要准确计算节点可用资源,可能不太实际。 或者,使用PodAntiAffinity,确保同节点上不会有两个相同类型的Pod,但如果有不同类型的Pod,可能还是会被调度。 所以,最可靠的方法可能是: - 通过污点和容忍,限制只有特定Pod能调度到该节点,并且仅部署个这样的Pod。 但如果有其他Pod也配置了相同的容忍和节点选择器,仍然会被调度。因此,这需要管理员控制,确保只有个Pod使用该配置。 综上,Kubernetes本身没有直接限制个节点只能运行个Pod的机制,但可以通过组合节点选择、污点、容忍和资源管理来实现近似效果。管理员需要谨慎配置,并确保不会部署其他Pod到该节点。</think>在Kubernetes中配置单个服务器(节点)仅运行个Pod,可以通过以下方法实现: --- ### **方法:节点污点(Taint)与容忍(Toleration)** 1. **标记节点并设置污点** 通过污点阻止其他Pod调度到该节点: ```bash kubectl taint nodes <节点名称> dedicated=singleton:NoSchedule ``` 2. **为Pod配置容忍和节点选择器** 在Pod的YAML文件中添加以下字段: ```yaml spec: tolerations: - key: "dedicated" operator: "Equal" value: "singleton" effect: "NoSchedule" nodeSelector: dedicated: singleton ``` 这样只有明确容忍此污点的Pod才能被调度到该节点[^2]。 --- ### **方法二:静态Pod(Static Pod)** 将Pod定义文件直接放置在节点的静态Pod目录中(如`/etc/kubernetes/manifests/`),由kubelet直接管理: 1. **生成Pod配置文件** ```bash kubectl run static-pod --image=nginx --dry-run=client -o yaml > static-pod.yaml ``` 2. **将文件复制到节点目录** ```bash scp static-pod.yaml <节点IP>:/etc/kubernetes/manifests/ ``` 该Pod会由节点自动创建,且不会被Kubernetes调度器干预[^3]。 --- ### **方法三:资源配额与独占性调度** 1. **通过资源请求独占节点资源** 在Pod中声明资源请求等于节点可用资源: ```yaml spec: containers: - name: myapp image: nginx resources: requests: cpu: "4" # 等于节点CPU核心数 memory: "8Gi" # 等于节点可用内存 ``` 这样其他Pod因资源不足无法调度到该节点。 2. **结合节点亲和性** 使用`nodeAffinity`确保Pod仅绑定到特定节点: ```yaml affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: dedicated operator: In values: ["singleton"] ``` --- ### **方法四:Pod反亲和性(Pod Anti-Affinity)** 阻止同节点运行多个同类Pod: ```yaml affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: ["myapp"] topologyKey: "kubernetes.io/hostname" ``` 此配置确保同节点上不会调度多个带`app=myapp`标签的Pod[^2]。 --- ### **验证配置** 1. 查看Pod调度结果: ```bash kubectl get pods -o wide ``` 2. 检查节点污点和标签: ```bash kubectl describe node <节点名称> ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值