一、背景
二、抢占机制的位置
三、抢占机制的原理
四、总结
一、背景
在Android系统中,当一个Client去open Camera时,可能会存在有其他Client也在使用camera的情况,然后由于设备之间的冲突关系或者系统带宽资源因素的限制,不得不进行一次角逐,将某些Client与其所连接的Camera断开以让出资源
假如现在系统中client A正在使用Camera,而此时Client B也要去open Camera,就会走到Android的抢占处理机制,抢占机制的决策结论可能会是以下三者之一:
1、Client A 与其所连接的camera断开,Client B 成功打开Camera;
2、Client B 打开camera失败,A继续使用Camera;
3、Client A 继续正常使用Camera,Client B 也能open成功。
二、抢占机制的位置
抢占机制位于Android Framework层,在/frameworks/av/services/camera/libcameraservice/CameraService.cpp中。当某个Client去open Camera时会走到里面的status_t handleEvictionsLocked() 函数,这个函数就是抢占机制的实现位置,其flow大致如下:
connectDevice()------->connectHelper--------->handleEvictiosLocked()
log:
CameraService: CameraService::connect call (PID -1 “com.tencent.mm”, camera ID 1) and Camera API version 1
CameraService: CameraService::connect X (PID 16095) rejected (existing client(s) with higher priority).
CameraService: Conflicts with: Device 0, client package com.xxx.camera (PID 24543, score -1000, state 2)
CameraBase: An error occurred while connecting to camera 1: Status(-8, EX_SERVICE_SPECIFIC): ‘8: connectHelper:1923: Too many cameras already open, cannot open camera “1”’
三、抢占机制的原理
接下来将详细叙述抢占机制的原理
抢占机制中的三个重要参数
Cost、Conflicting和Priority,
这三个关键因素都作为成员变量存放在ClientManager.h Class ClinetDescriptor中
template<class KEY, class VALUE>
class ClientDescriptor final {
…
private:
KEY mKey; //Camera ID
VALUE mValue; //sp
int32_t mCost; //打开这个Camera所占用的带宽
std::set mConflicting; //与Camera ID相冲突的那些Camera device的ID
ClientPriority mPriority; //进程的优先级,与mOwnerId绑定
int32_t mOwnerId; //Clinet pid
};
Conflicting:即冲突关系,如果两个Logic Camera所使用的物理sensor有交集,则称这两个Camera之间是Conflicing的,即两者为冲突设备。在Android中,这两个logic Camera是不能够同时工作的
Cost:打开特定Camera所需要占用的带宽(存在Cost=0的情况)
Priority:即Client的优先级,由oom_score_adj和state组成,两个值越小则client优先级越高
ClientDescriptor一旦构造完毕,除mPriority之外,其余的值不会发生改变
ClientManager.h ClientManager
template<class KEY, class VALUE, class LISTENER=DefaultEventListener<KEY, VALUE>>
Class ClientManager{
public:
static constexpr int32_t DEFAULT_MAX_COST=100; //系统资源的上限
void updatePriorities(const map<int32_t,ClientPriority>& ownerPriorityList);
std::vector<std::shared_ptr<ClientDescriptor<KEY, VALUE>>>wouldEvict(const std::shared_ptr<ClientDescriptor<KEY, VALUE>>& client) const;
status_t waitUntilRemoved(const std::shared_ptr<ClientDescriptor<KEY, VALUE>> client, nsecs_t timeout) const;
…………
Private:
int32_t mMaxCost;
//记录了当前哪些client在使用哪些Camera
std::vector<std::shared_ptr<ClientDescriptor<KEY,VALUE>>> mClients;
………
}
ClientManager里面的成员变量vector<std::shared_ptr<ClientDescriptor<KEY,VALUE>>> mClients,记录了当前有哪些ClientDesriptor,即描述了当前有哪些Client在使用哪些Camera。
P.s
CameraService、ClientManager和ClientDescriptor关系如下图所示
抢占机制的决策流程
ClientManager又是class CameraService的成员变量,会遍历ClientManager::mClients成员,来决定哪些Client会被加入驱逐名单。
// Build eviction list of clients to remove
for (const auto& i : mClients) {
const KEY& curKey = i->getKey();
int32_t curCost = i->getCost();
ClientPriority curPriority = i->getPriority();
int32_t curOwner = i->getOwnerId();
bool conflicting = (curKey == key || i->isConflicting(key) ||
client->isConflicting(curKey));
if (!returnIncompatibleClients) {
// Find evicted clients
if (conflicting && curPriority < priority) {
// Pre-existing conflicting client with higher priority exists
evictList.clear();
evictList.push_back(client);
return evictList;
} else if (conflicting && owner == curOwner) {
// Pre-existing conflicting client with the same client owner exists
// Open the same device twice -> most recent open wins
// Otherwise let the existing client wins to avoid behaviors difference
// due to how HAL advertising conflicting devices (which is hidden from
// application)
if (curKey == key) {
evictList.push_back(i);
totalCost -= curCost;
} else {
evictList.clear();
evictList.push_back(client);
return evictList;
}
} else if (conflicting || ((totalCost > mMaxCost && curCost > 0) &&
(curPriority >= priority) &&
!(highestPriorityOwner == owner && owner == curOwner))) {
// Add a pre-existing client to the eviction list if:
// - We are adding a client with higher priority that conflicts with this one.
// - The total cost including the incoming client's is more than the allowable
// maximum, and the client has a non-zero cost, lower priority, and a different
// owner than the incoming client when the incoming client has the
// highest priority.
evictList.push_back(i);
totalCost -= curCost;
}
} else {
// Find clients preventing the incoming client from being added
if (curPriority < priority && (conflicting || (totalCost > mMaxCost && curCost > 0))) {
// Pre-existing conflicting client with higher priority exists
evictList.push_back(i);
}
}
}
在每一轮的循环中:
1、先判断此次去open camera的client(暂且叫Cur)与当前的这个client(暂且叫 i 吧)分别对应的logical device是否为冲突设备
2、如果是冲突设备,则比较两个Client的优先级,如果当前的Cur的优先级低,则把Cur加入驱逐名单(即表明open Camera fail)
3、如果是冲突设备,但是其实cur与i是一个client,则分两种情形
3.1 如果是同一个logic Camera,则将 i 加入驱逐名单,并且计算出其让出的资源
3.2 否则,把Cur加入驱逐名单(即表明open Camera fail),为了避免行为的差异
4、如果是冲突设备或者现在所占用的系统资源已经超出了系统限制并且i所对应的Camera所占用的带宽大于0,Cur的优先级比 i 的优先级高,i与Cur不是同一个client,i 也并不具有最高优先级,则把 i 加入驱逐名单,并且计算出其让出的资源
四、总结
如果是冲突设备,需要分两种情况:
1、Cur与 i 是不同的client,则需要比较这两个client的优先级。如果Cur优先级比 i 小,则Cur就会被加入驱逐名单,意味此次抢占失败;如果Cur优先级比 i 高,则把 i 加入驱逐名单
2、Cur与 i 是相同的client(意味着优先级一样),如果是相同的Camera Id,则 i 进入驱逐名单,并扣去它所消耗的带宽资源;否则Cur进入驱逐名单。(即open同一个camera device,则最近去open的client(即Cur)获胜;如果不是同一个camera device,则让已经存在的client获胜,即client i与它已连接的camera device保持连接,以避免行为差异)
如果不是冲突设备,这时就需要看系统还剩多少带宽资源,如果带宽资源不够,则就比较Cur和 i的优先级,如果 i 优先级不高于Cur(如果相等,则看是否是一个client并且是否是最高优先级的),则把 i 加入驱逐名单;否则,就什么也不做,进入下一个client的比较
最后,在按上述规则遍历完所有的client后,如果占用系统资源还是超出了系统限制,并且Cur又没有最高优先级的情况下,就会把驱逐名单清空,把Cur加入驱逐名单,这意味着此次抢占失败,open不了Camera device
其实可以发现,一个clinet无论是抢占不成功还是被别的client挤下去,都是优先级不够造成的。
最后总结起来就是一个client在去open Camera的过程中,要去处理设备之间的冲突关系和抢占系统带宽资源,而它们所使用的“武器”就是自己的优先级,只要自己的优先级够高,就可以抢占成功或者不被别的client挤下去。简而言之,如果想要抢占成功,则最终一定要开辟出这样一个条件:没有冲突设备在工作、带宽资源充足以及不发生其他意外情况(比如查询操作没有超时)。
文章来源 CSDN:
https://blog.csdn.net/wk314061331/article/details/125783531
作者:离岛牧羊人
推荐阅读:
深圳上班,
从事Android Camera相关软件开发工作,
公众号记录生活和工作的点滴,
欢迎加入“小驰成长圈”,期待和你相遇~