进程调度之入队列

本文深入剖析了进程从创建到运行状态的转变过程,包括获取CPU资源、选择合适CPU、更新进程状态、激活任务队列等关键步骤,并详细展示了enqueue_task、enqueue_entity等函数的工作原理。

 

wake_up_new_task
	=>int cpu __maybe_unused = get_cpu();
		=>#define get_cpu()		({ preempt_disable(); smp_processor_id(); })
	=>cpu = select_task_rq(p, SD_BALANCE_FORK, 0);//根据负载选择一个合适的CPU
	=>set_task_cpu(p, cpu);//讲p进程绑定到该cpu
	=>rq = cpu_rq(cpu);//取该CPU的运行队列
	=>raw_spin_lock_irqsave(&rq->lock, flags);
	=>p->state = TASK_RUNNING;//进程进入运行态
	=>update_rq_clock(rq);
	=>activate_task(rq, p, 0);//新进程入队列
		=>enqueue_task(rq, p, wakeup, false);
			=>p->sched_class->enqueue_task(rq, p, wakeup, head);//Sched_fair.c (kernel):	.enqueue_task		= enqueue_task_fair,
				=>for_each_sched_entity(se) {
					if (se->on_rq)
						break;
					cfs_rq = cfs_rq_of(se);
					enqueue_entity(cfs_rq, se, flags);
						=>if (!(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_MIGRATE))
								se->vruntime += cfs_rq->min_vruntime;
						=>update_curr(cfs_rq);
						account_entity_enqueue(cfs_rq, se);
						=>if (flags & ENQUEUE_WAKEUP) {
							place_entity(cfs_rq, se, 0);
							enqueue_sleeper(cfs_rq, se);
						=>update_stats_enqueue(cfs_rq, se);
						=>check_spread(cfs_rq, se);
						=>if (se != cfs_rq->curr)
							__enqueue_entity(cfs_rq, se);//进红黑树
						}
					flags = ENQUEUE_WAKEUP;
				}
			=>p->se.on_rq = 1;
		=>inc_nr_running(rq);
	=>check_preempt_curr(rq, p, WF_FORK);
	=>p->sched_class->task_woken(rq, p);
	=>task_rq_unlock(rq, &flags);
	=>put_cpu();
		=>#define put_cpu()		preempt_enable()

 

<think>我们正在讨论UnrealEngine中的tick函数和ENQUEUE_RENDER_COMMAND的使用与实现。用户的问题集中在如何在渲染线程中执行命令,这通常涉及到将游戏线程(或任何非渲染线程)中的指令安全地传递到渲染线程。首先,我们需要明确几个概念:1.UE的渲染线程:负责处理所有与渲染相关的操作,是一个单独的线程。2.ENQUEUE_RENDER_COMMAND:一个宏,用于将一段代码(lambda)封装成一个渲染命令,并提交到渲染线程执行。3.Tick函数:在游戏线程中每帧执行,用于游戏逻辑。常见的应用场景:在游戏线程(例如在tick函数中)计算了一些数据,需要传递给渲染线程,比如更新某个动态纹理或顶点缓冲等。实现步骤:1.在需要的地方(比如tick函数中)使用ENQUEUE_RENDER_COMMAND宏来提交一个渲染命令。2.在渲染命令中执行需要运行在渲染线程上的代码。注意事项:-由于渲染命令在渲染线程执行,所以不能访问游戏线程的数据(除非这些数据是线程安全的)。通常,我们会将需要的数据复制到渲染线程。-数据同步是需要注意的点。下面是一个简单的代码示例,展示在tick函数中使用ENQUEUE_RENDER_COMMAND:```cppvoidAMyActor::Tick(floatDeltaTime){Super::Tick(DeltaTime);//假设我们在游戏线程中计算了一些数据,比如一个颜色值FLinearColorNewColor=CalculateColorBasedOnGameState();//使用ENQUEUE_RENDER_COMMAND将更新颜色的命令提交到渲染线程ENQUEUE_RENDER_COMMAND(UpdateColorCommand)([NewColor](FRHICommandListImmediate&RHICmdList){//在渲染线程中执行//这里假设我们有一个全局的渲染资源,比如一个纹理,需要更新其颜色//注意:在渲染线程中操作渲染资源是安全的RenderThreadUpdateColor(NewColor);});}```其中,`RenderThreadUpdateColor`是在渲染线程中执行的一个函数,它需要定义在渲染模块中(如一个继承自FGlobalShader的类,或者是在渲染模块的头文件中声明并在.cpp中实现,并且需要包含在渲染模块的启动/关闭管理之中)。另外,如果需要在渲染命令中访问UObject或者游戏线程的其他对象,需要非常小心,因为渲染线程不能直接访问UObject。通常的做法是使用共享指针来传递数据,或者使用渲染线程安全的资源(如FRenderResource的子类)。关于实现细节:ENQUEUE_RENDER_COMMAND宏定义在Engine.h中,它实际上创建了一个FRenderCommand的对象,该对象捕获了传递的lambda函数,并将其加入到渲染线程的命令队列中。渲染线程会在每一帧开始渲染之前,从队列中取出命令并执行。性能考虑:1.避免在每一帧都提交大量的渲染命令,这可能会对性能产生影响。2.尽量将多个更新合并成一个命令提交。进阶使用:有时候,我们可能需要传递复杂的参数,这些参数可能需要生命周期管理。对于需要在多个线程之间传递的数据,使用TSharedPtr或TRefCountPtr管理引用计数是一个常见的方法。下面是一个传递动态数组的例子:```cppvoidAMyActor::UpdateDynamicPositions(constTArray<FVector>&Positions){//创建共享指针包裹的数据副本,确保在渲染命令执行时数据依然有效TSharedPtr<TArray<FVector>,ESPMode::ThreadSafe>PositionsCopy=MakeShared<TArray<FVector>,ESPMode::ThreadSafe>(Positions);ENQUEUE_RENDER_COMMAND(UpdatePositionsCommand)([PositionsCopy](FRHICommandListImmediate&RHICmdList){//在渲染线程中使用PositionsCopy//注意:由于是共享指针,可以确保数据在渲染命令执行期间有效RenderThreadUpdatePositions(*PositionsCopy);});}```注意:这里使用共享指针并且指定为线程安全(ESPMode::ThreadSafe)是为了确保引用计数的操作是线程安全的。另外,如果需要在渲染命令执行完成后,在游戏线程得到通知,可以使用FGraphEvent。但这种情况较少见,因为渲染命令执行后通常不需要通知游戏线程。总结:在UE中使用ENQUEUE_RENDER_COMMAND是在渲染线程中执行命令的标准方法。它提供了一种线程安全的机制将命令从其他线程(主要是游戏线程)传递到渲染线程。在编写渲染命令时,要注意数据同步和生命周期管理。相关问题1.如何确保在渲染命令中使用的数据是安全的?2.除了ENQUEUE_RENDER_COMMAND,还有哪些其他方式可以向渲染线程传递命令?3.在渲染线程中可以直接修改UObject的数据吗?为什么?4.如何传递大量数据到渲染线程,例如动态顶点数据?现在我们生成相关问题:§§相关问题§§1.在虚幻引擎中,如何正确处理多线程数据同步以避免渲染命令访问无效数据?2.除了ENQUEUE_RENDER_COMMAND,虚幻引擎还提供了哪些方法用于跨线程操作?3.在渲染线程中,能否直接访问或修改UObject?为什么?4.如何优化频繁的渲染命令提交,以减少性能开销?5.如何使用ENQUEUE_RENDER_COMMAND传递复杂的参数(如动态数组或结构体)?</think> 以下是关于虚幻引擎(UE)中 `tick` 函数与 `ENQUEUE_RENDER_COMMAND` 的使用方法和实现细节的完整解析: ### 1. Tick函数的基础原理 - **定义**:`Tick` 是`AActor`或`UActorComponent`的可重写函数,在游戏线程每帧调用 - **调用位置**: ```cpp virtual void Tick(float DeltaTime) override; ``` - **特性**: - 默认在主游戏线程执行(除非手动设置为多线程tick) - 适合处理非渲染逻辑(如物理计算、游戏状态更新) - 与渲染线程异步执行[^1] ### 2. ENQUEUE_RENDER_COMMAND 核心机制 #### ▶ 作用原理 ```mermaid graph LR A[游戏线程] --> B[ENQUEUE_RENDER_COMMAND] --> C{渲染命令队列} --> D[渲染线程执行] ``` - **跨线程通信**:将Lambda表达式封装的命令提交到渲染线程队列 - **线程安全**:通过命令队列实现线程间数据隔离 - **同步点**:在渲染线程的帧开始前消费队列中的命令[^2] #### ▶ 代码实现结构 ```cpp ENQUEUE_RENDER_COMMAND(CommandName)( [CaptureList](FRHICommandListImmediate& RHICmdList) { // 此处代码在渲染线程执行 // 可安全访问渲染资源 } ); ``` ### 3. 典型使用场景与示例 #### ▶ 动态更新材质参数 ```cpp void AMyActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); // 游戏线程计算新颜色 FLinearColor DynamicColor = CalculateDynamicColor(); // 提交到渲染线程 ENQUEUE_RENDER_COMMAND(UpdateMaterialParams)( [MatInstance = MaterialInstance, DynamicColor](FRHICommandListImmediate& RHICmdList) { // 渲染线程安全更新 MatInstance->SetVectorParameterValue("ColorParam", DynamicColor); } ); } ``` #### ▶ 纹理异步更新 ```cpp // 游戏线程 void UpdateTextureData() { TArray<FColor> PixelData = GeneratePixels(); ENQUEUE_RENDER_COMMAND(FillTexture)( [Tex = MyTexture, Pixels = MoveTemp(PixelData)](auto& RHICmdList) { // 渲染线程更新纹理 RHIUpdateTexture2D(Tex->Resource->TextureRHI, 0, Pixels); } ); } ``` ### 4. 关键注意事项 1. **数据捕获规则**: - 按值捕获基础类型(int, float等) - 引用捕获需确保对象生命周期(建议用TSharedPtr管理) - 避免捕获UObject原始指针 2. **内存安全**: ```cpp // 危险!对象可能已被GC ENQUEUE_RENDER_COMMAND(...)( [this] { /* 使用this->Member */ } // 错误! // 正确方式 TWeakObjectPtr<UObject> SafePtr = this; ENQUEUE_RENDER_COMMAND(...)( [SafePtr] { if(SafePtr.IsValid()){...} } ``` 3. **性能优化**: - 合并高频小命令为单次批处理 - 避免在tick中频繁提交命令(每帧≤1次为宜) - 使用`FlushRenderingCommands()`强制同步(慎用) ### 5. 进阶用法:渲染资源创建 ```cpp ENQUEUE_RENDER_COMMAND(CreateBuffer)( [](FRHICommandListImmediate& RHICmdList) { FRHIResourceCreateInfo CreateInfo; VertexBufferRHI = RHICreateVertexBuffer(..., CreateInfo); } ); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值