这个例子是从安卓Android10.0源码logd模块摘取,文件路径:yukawa\system\core\liblog\logd_writer.cpp
这里详细解析什么是原子变量和原子操作,只对源码中对原子变量的运用做一些简单的分析。
这个文件的代码是安卓中log模块接口部分,即应用写log时通过套接字需要将log发送到log服务器,这里是应用client端初始化时对socket进行初始化。对于客户端进程而言,这个初始化过程而言只需要初始化一次就可以了。然而在多线程环境下就有可能造成多次初始化。
对于这种场景可以使用原子变量来避免这个问题。
首先俩看一下socket初始化代码,逻辑很简单:先检查全局的sock文件描述符是否合法,如果合法就意味着这个socket已经初始化过了,可以直接返回;如果socket非法,是一个负值,那就意味着该sock没有初始化,那就创建一个新的socket,并把它的fd赋值给全局的sock。
这个方案在多线程环境下,比如当两个线程同时执行初始化的时候是有可能创建了两个socket。当然我们可以通过加锁来避免这个问题,但是频繁会导致性能收到影响。这个时候可以使用原子变量的原子操作来解决问题。
首先我们可以将sock声明为原子变量,类型为atomic_int,用来保存全局socket的fd,安卓dlog源码定义如下:
union android_log_context_union {
void* priv;
atomic_int sock;
atomic_int fd;
struct listnode* node;
atomic_uintptr_t atomic_pointer;
};
android_log_transport_write 这个结构体定义了写log的接口函数指针,其中open函数就是用来做初始化的
struct android_log_transport_write {
struct listnode node;
const char* name; /* human name to describe the transport */
unsigned logMask; /* mask cache of available() success */
union android_log_context_union context; /* Initialized by static allocation */
int (*available)(log_id_t logId); /* Does not cause resources to be taken */
int (*open)(); /* can be called multiple times, reusing current resources */
void (*close)(); /* free up resources */
/* write log to transport, returns number of bytes propagated, or -errno */
int (*write)(log_id_t logId, struct timespec* ts, struct iovec* vec,
size_t nr);
};
logdOpen()函数的具体实现如下:
sock 先用一个负数-EBADF来初始化,表示该socket还没初始化。
struct android_log_transport_write logdLoggerWrite = {
.node = {&logdLoggerWrite.node, &logdLoggerWrite.node},
.context.sock = -EBADF, //初始化为负数
.name = "logd",
.available = logdAvailable,
.open = logdOpen,
.close = logdClose,
.write = logdWrite,
};
/* log_init_lock assumed */
static int logdOpen() {
int i, ret = 0;
i = atomic_load(&logdLoggerWrite.context.sock); //原子地加载sock,然后判断sock的值
if (i < 0) { //i<0,意味着没有初始化,于是创建一个新的socket
int sock = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
if (sock < 0) {
ret = -errno;
} else {
struct sockaddr_un un;
memset(&un, 0, sizeof(struct sockaddr_un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, "/dev/socket/logdw");
if (TEMP_FAILURE_RETRY(connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) <
0) {
ret = -errno;
switch (ret) {
case -ENOTCONN:
case -ECONNREFUSED:
case -ENOENT:
i = atomic_exchange(&logdLoggerWrite.context.sock, ret);
[[fallthrough]];
default:
break;
}
close(sock);
} else {
ret = atomic_exchange(&logdLoggerWrite.context.sock, sock); //关键的地方在这里,使用atomic_exchange函数,该函数返回值为原子变量修改前的值
if ((ret >= 0) && (ret != sock)) {
close(ret); //如果ret为非负数而且不等于上一步创建的socket的fd,那就意味着这是另一个线程创建的socket,这里需要把它的close掉。
}
ret = 0;
}
}
}
return ret;
}
上面这部分代码使用了atomic_exchange函数,它的声明如下:
C atomic_exchange(volatile A *object, C value);
其中A是原子变量类型,C是对应的值类型,比如A是atomic_int类型,那么C就是int类型.
object是原子变量的指针,value是要给object指向的原子变量所赋的值。
返回值为这个赋值操作执行前object指向对象的值。
源码中关闭socket的代码,当要关闭socket时要给sock赋一个负值,然后再close。那为了避免关闭两次一个socket,这里使用atomic_exchange原子地赋值之后,检查返回值,如果返回值为非负,那就说明该fd之前还没有close,这种情况就可以直接close掉。但如果返回值为负数,那就说明赋值前就已经为负数了,此时就不需要再close。
static void __logdClose(int negative_errno) {
int sock = atomic_exchange(&logdLoggerWrite.context.sock, negative_errno);
if (sock >= 0) {
close(sock);
}
}
而鸿蒙系统的log模块也是使用类似的处理,其中socket的初始化代码如下。鸿蒙的处理方法是使用了cas对比转换算法,compare_exchange_strong,这个函数意思是只有fdhandle为-1时才会赋值。如果fdhandle不为-1那就意味着已经有其它线程已经进行了初始化,这时候defaultvalue的值就会被更新为fdhandle的值。
namespace OHOS {
namespace HiviewDFX {
int DgramSocketClient::CheckSocket()
{
if (fdHandler >= 0) {
return fdHandler;
}
int fd = GenerateFD();
if (fd < 0) {
return fd;
}
int ret;
int defaultValue = -1;
fdHandler.compare_exchange_strong(defaultValue, fd); //fdHandler为atomic_int类型
if (defaultValue != -1) {
ret = defaultValue;
close(fd);
} else {
ret = fd;
setHandler(fd);
Connect();
}
return ret;
}
} // namespace HiviewDFX
} // namespace OHOS