AndroidFramework 之啟動(dòng) ServiceManager

閱讀須知

本文源碼基于 Android 10,涉及相關(guān)源碼如下。

system/core/
    - init/init.cpp
    - rootdir/init.rc
    
frameworks/native/cmds/servicemanager/
    - Android.bp
    - binder.c
    - service_manager.c
    - servicemanager.rc

概述

ServiceManagaerBinder 的守護(hù)進(jìn)程,在 Binder 機(jī)制中起著重要的作用。本文將從源碼的角度對(duì)其進(jìn)行分析,整體流程如下:

  1. ServiceManager 的啟動(dòng);
  2. 打開(kāi) Binder 驅(qū)動(dòng);
  3. 設(shè)置上下文管理者;
  4. 進(jìn)入循環(huán)。
image.png

時(shí)序圖如下。

image.png

1. ServiceManager 的啟動(dòng)

先來(lái)看看 ServiceManager 是如何啟動(dòng)的:

  1. init 進(jìn)程解析 init.rc,觸發(fā) trigger init;
  2. 執(zhí)行 start servicemanager 啟動(dòng) ServiceManager。
image.png

1.1 觸發(fā) trigger init

Zygote 一文中說(shuō)過(guò),init 進(jìn)程啟動(dòng)的第二階段會(huì)解析 init.rc 文件。

在這之后會(huì)觸發(fā) trigger init。

// system/core/init/init.cpp

int SecondStageMain(int argc, char** argv) {
    // 解析 init.rc 文件
    LoadBootScripts(am, sm);
    // 觸發(fā) trigger init
    am.QueueEventTrigger("init");
}

結(jié)合 init.rc 看看 action init 做了什么。

# system/core/rootdir/init.rc

on init
    # 啟動(dòng) service servicemanager 
    start servicemanager

1.2 啟動(dòng) ServiceManager

當(dāng)觸發(fā) trigger init 后,會(huì)啟動(dòng) servicemanager 服務(wù),其聲明如下。

# frameworks/native/cmds/servicemanager/servicemanager.rc

# 對(duì)應(yīng)執(zhí)行的文件為 /system/bin/servicemanager
service servicemanager /system/bin/servicemanager
    class core animation
    user system
    group system readproc
    critical
    # 當(dāng) servicemanager 重啟的時(shí)候會(huì)重啟下列服務(wù)
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart audioserver
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart inputflinger
    onrestart restart drm
    onrestart restart cameraserver
    onrestart restart keystore
    onrestart restart gatekeeperd
    onrestart restart thermalservice
    writepid /dev/cpuset/system-background/tasks
    shutdown critical

對(duì)應(yīng)的執(zhí)行文件為 /system/bin/servicemanager,在編譯前位于 frameworks/native/cmds/servicemanager 下,來(lái)看看 Android.bp。

// frameworks/native/cmds/servicemanager/Android.bp

cc_binary {
    name: "servicemanager",
    srcs: [
        "service_manager.c",
        "binder.c",
    ],
    init_rc: ["servicemanager.rc"],
}

其對(duì)應(yīng)的源碼為 service_manager.cbinder.c,入口函數(shù) main() 位于 servicemanager.c。

2. 打開(kāi) Binder 驅(qū)動(dòng)

啟動(dòng)完 ServiceManager 后會(huì)打開(kāi) Binder 驅(qū)動(dòng)。

image.png

main() 中首先調(diào)用 binder_open()

// frameworks/native/cmds/servicemanager/service_manager.c

int main(int argc, char** argv)
{
    char *driver;
    driver = "/dev/binder";
    // 調(diào)用 binder_open() 打開(kāi) binder 驅(qū)動(dòng),并申請(qǐng) 128 kb 內(nèi)存
    bs = binder_open(driver, 128*1024);
}

binder_open() 主要做了如下事情:

  1. 初始化結(jié)構(gòu)體 binder_state
  2. 系統(tǒng)調(diào)用 open() 打開(kāi) /dev/binder,獲得文件描述符 fd
  3. 系統(tǒng)調(diào)用 ioctl() 獲取 binder 版本并做版本比對(duì);
  4. 系統(tǒng)調(diào)用 mmap() 內(nèi)存映射 128kb 的空間供 ServiceManager 使用。

2.1 初始化 binder_state

給結(jié)構(gòu)體 binder_state 分配內(nèi)存。

// frameworks/native/cmds/servicemanager/binder.c

struct binder_state
{
    // 打開(kāi) binder 驅(qū)動(dòng)獲取的文件描述符
    int fd;
    // 指向 mmap() 內(nèi)存映射地址
    void *mapped;
    // 映射內(nèi)存大小
    size_t mapsize;
};

struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    struct binder_state *bs;
    // 給 bs 分配內(nèi)存
    bs = malloc(sizeof(*bs));
    if (!bs) {
        return NULL;
    }
}

2.2 打開(kāi) Binder 驅(qū)動(dòng)

系統(tǒng)調(diào)用 open() 打開(kāi) /dev/binder,如果打開(kāi)驅(qū)動(dòng)失敗,則執(zhí)行 fail_open 釋放內(nèi)存。

// frameworks/native/cmds/servicemanager/binder.c

struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    // 調(diào)用 open() 打開(kāi) binder 驅(qū)動(dòng)
    bs->fd = open(driver, O_RDWR | O_CLOEXEC);
    if (bs->fd < 0) {
        goto fail_open;
    }
    
fail_open:
    // 釋放 bs 內(nèi)存
    free(bs);
    return NULL;
}

簡(jiǎn)單的解釋一下什么是系統(tǒng)調(diào)用?

由于需要限制不同的程序之間的訪問(wèn)能力,防止程序獲取別的程序的內(nèi)存數(shù)據(jù),CPU 劃分出兩個(gè)權(quán)限等級(jí),用戶態(tài)內(nèi)核態(tài)。

  • 用戶態(tài) 只能受限的訪問(wèn)內(nèi)存,不允許訪問(wèn)外圍設(shè)備,占用 CPU 的能力被剝削,CPU 資源可以被其他程序獲取
  • 內(nèi)核態(tài) 可以訪問(wèn)內(nèi)存所有數(shù)據(jù),包括外圍設(shè)備,CPU 可以將自己從一個(gè)程序切換到另外一個(gè)程序

所有的用戶程序都是運(yùn)行在用戶態(tài),但有時(shí)需要做一些內(nèi)核態(tài)的事情,而唯一可以做這些事情的就是操作系統(tǒng),所以程序需要向操作系統(tǒng)發(fā)起請(qǐng)求,以程序的名字來(lái)執(zhí)行這些操作。這時(shí)就需要一個(gè)從用戶態(tài)切換到內(nèi)核態(tài)但不能控制內(nèi)核態(tài)中執(zhí)行的機(jī)制,這種機(jī)制就是 系統(tǒng)調(diào)用。

2.3 檢查 Binder 版本

系統(tǒng)調(diào)用 ioctl() 傳入 BINDER_VERSION 命令獲取 Binder 驅(qū)動(dòng)版本,對(duì)比版本是否一致,不一致則執(zhí)行 fail_open 釋放內(nèi)存。

// frameworks/native/cmds/servicemanager/binder.c

struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    struct binder_version vers;
    // 調(diào)用 ioctl() 獲取 Binder 版本
    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
        goto fail_open;
    }
fail_open:
    // 釋放 bs 內(nèi)存
    free(bs);
    return NULL;
}

2.4 內(nèi)存映射

系統(tǒng)調(diào)用 mmap() 映射 128kb 的內(nèi)存空間,即把 Binder 驅(qū)動(dòng)文件的 128kb 映射到內(nèi)存空間供 ServiceManager 使用,內(nèi)存映射失敗則執(zhí)行 fail_map,關(guān)閉 fd 并釋放內(nèi)存。

// frameworks/native/cmds/servicemanager/binder.c

struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    bs->mapsize = mapsize;
    // 調(diào)用 mmap() 做內(nèi)存映射
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        goto fail_map;
    }
    return bs;
fail_map:
    // 關(guān)閉 fd
    close(bs->fd);
fail_open:
    // 釋放 bs 內(nèi)存
    free(bs);
    return NULL;
}

ServiceManager 進(jìn)程 mmap 的內(nèi)存大小可以通過(guò) adb shell 命令查看。

$ ps -eT | grep servicemanager
$ cat /proc/1653/maps
image.png
image.png

可以看到內(nèi)存映射地址為 0xf10f8000 ~ 0xf1118000,差為 0x20000 即十進(jìn)制的 128kb

3. 設(shè)置上下文管理者

打開(kāi) Binder 驅(qū)動(dòng)后會(huì)將 ServiceManager 設(shè)置為上下文管理者。

image.png

調(diào)用 binder_become_context_manager()。

// frameworks/native/cmds/servicemanager/service_manager.c

int main(int argc, char** argv)
{
    // 成為 context manager
    if (binder_become_context_manager(bs)) {
        return -1;
    }
}

// frameworks/native/cmds/servicemanager/binder.c

int binder_become_context_manager(struct binder_state *bs)
{
    struct flat_binder_object obj;
    memset(&obj, 0, sizeof(obj));
    obj.flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX;
    // 系統(tǒng)調(diào)用 ioctl() 傳入 BINDER_SET_CONTEXT_MGR_EXT 設(shè)置安全的上下文管理者
    int result = ioctl(bs->fd, BINDER_SET_CONTEXT_MGR_EXT, &obj);

    if (result != 0) {
        // 失敗則傳入 BINDER_SET_CONTEXT_MGR 設(shè)置上下文管理者
        result = ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
    }
    return result;
}

android 10 新增 BINDER_SET_CONTEXT_MGR_EXT 命令來(lái)設(shè)置安全的上下文管理者,如果設(shè)置失敗,則使用原有的 BINDER_SET_CONTEXT_MGR 命令來(lái)設(shè)置上下文管理者,兩者區(qū)別在于是否攜帶參數(shù)。

4. 進(jìn)入循環(huán)

最后會(huì)進(jìn)入循環(huán),從 Binder 驅(qū)動(dòng)讀取和解析數(shù)據(jù)。

image.png

調(diào)用 binder_loop() 進(jìn)入循環(huán),不斷地通過(guò)系統(tǒng)調(diào)用 ioctl()Binder 驅(qū)動(dòng)讀取數(shù)據(jù),并通過(guò) binder_parse() 進(jìn)行數(shù)據(jù)解析。

// frameworks/native/cmds/servicemanager/service_manager.c

int main(int argc, char** argv)
{
    // 調(diào)用 binder_loop() 進(jìn)入循環(huán),注意這里的
    binder_loop(bs, svcmgr_handler);
}

// frameworks/native/cmds/servicemanager/binder.c

void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    // 128kb 的緩存區(qū)
    uint32_t readbuf[32];
    
    // 寫(xiě)入 BC_ENTER_LOOPER 命令通知 binder 驅(qū)動(dòng)即將進(jìn)入循環(huán)
    readbuf[0] = BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(uint32_t));

    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        // 系統(tǒng)調(diào)用 ioctl() 讀取 binder 驅(qū)動(dòng)傳遞的數(shù)據(jù)放到 readbuf
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        // 調(diào)用 binder_parse() 解析數(shù)據(jù)
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
    }
}

注意這里調(diào)用 binder_loop() 傳入的 svcmgr_handler(),后面會(huì)使用到。

4.1 binder_write()

binder_write() 會(huì)封裝 struct binder_write_read,并通過(guò)系統(tǒng)調(diào)用 ioctl() 將對(duì)應(yīng)的命令傳遞給 Binder 驅(qū)動(dòng)。

// frameworks/native/cmds/servicemanager/binder.c

int binder_write(struct binder_state *bs, void *data, size_t len)
{
    struct binder_write_read bwr;
    int res;
    // 封裝寫(xiě)入數(shù)據(jù)
    bwr.write_size = len;
    bwr.write_consumed = 0;
    bwr.write_buffer = (uintptr_t) data;
    // 清除讀取數(shù)據(jù)
    bwr.read_size = 0;
    bwr.read_consumed = 0;
    bwr.read_buffer = 0;
    // 系統(tǒng)調(diào)用 ioctl() 傳入 BINDER_WRITE_READ 命令表示進(jìn)行讀寫(xiě)操作
    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    return res;
}

4.2 binder_parse()

binder_parse() 用來(lái)解析從 Binder 驅(qū)動(dòng)讀取到的數(shù)據(jù),然后根據(jù)不同的命令執(zhí)行對(duì)應(yīng)的操作。

// frameworks/native/cmds/servicemanager/binder.c

int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uintptr_t ptr, size_t size, binder_handler func)
{
    int r = 1;
    uintptr_t end = ptr + (uintptr_t) size;
    // whilte 循環(huán)讀取處理命令,可能存在多個(gè)命令
    while (ptr < end) {
        // 讀取命令,命令占用 readbuf 的 4 個(gè)字節(jié)
        uint32_t cmd = *(uint32_t *) ptr;
        ptr += sizeof(uint32_t);
        // 根據(jù)命令做對(duì)應(yīng)的操作
        switch(cmd) {
        case BR_NOOP:
            break;
        case BR_TRANSACTION_COMPLETE:
            break;
        case BR_INCREFS:
        case BR_ACQUIRE:
        case BR_RELEASE:
        case BR_DECREFS:
            break;
        case BR_TRANSACTION_SEC_CTX:
        case BR_TRANSACTION:
            break;
        case BR_REPLY: 
            break;
        case BR_DEAD_BINDER:
            break;
        case BR_FAILED_REPLY:
            break;
        case BR_DEAD_REPLY:
            break;
        default:
            return -1;
        }
    }

    return r;
}

因?yàn)?cmd 命令可能有多個(gè),所以通過(guò) while 循環(huán)每次處理一個(gè) cmd 命令,多 cmd 的結(jié)構(gòu)大致如下圖所示。

image.png

這里重點(diǎn)看下 BR_TRANSACTION 命令。

4.2.1 BR_TRANSACTION

BR_TRANSACTIONBinder 驅(qū)動(dòng)向 Server 端發(fā)送請(qǐng)求數(shù)據(jù)。

// frameworks/native/cmds/servicemanager/binder.c

int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uintptr_t ptr, size_t size, binder_handler func)
{
    switch(cmd) {
    case BR_TRANSACTION_SEC_CTX:
    case BR_TRANSACTION: {
        struct binder_transaction_data_secctx txn;
        // BR_TRANSACTION_SEC_CTX
        if (cmd == BR_TRANSACTION_SEC_CTX) {
            // 拷貝 binder_transaction_data_secctx 到 transaction_data
            memcpy(&txn, (void*) ptr, sizeof(struct binder_transaction_data_secctx));
            ptr += sizeof(struct binder_transaction_data_secctx);
        } else /* BR_TRANSACTION */ {
            // 拷貝 binder_transaction_data 到 transaction_data
            memcpy(&txn.transaction_data, (void*) ptr, sizeof(struct binder_transaction_data));
            ptr += sizeof(struct binder_transaction_data);
            txn.secctx = 0;
        }

        if (func) {
            unsigned rdata[256/4];
            struct binder_io msg;
            struct binder_io reply;
            int res;

            // reply 初始化
            bio_init(&reply, rdata, sizeof(rdata), 4);
            // 從 txn.transaction_data 解析出 binder_io 的信息,存入 msg
            bio_init_from_txn(&msg, &txn.transaction_data);
            // svcmgr_handler() 處理對(duì)應(yīng)的操作
            res = func(bs, &txn, &msg, &reply);
            if (txn.transaction_data.flags & TF_ONE_WAY) {
                // 如果是 TF_ONE_WAY 處理,則釋放數(shù)據(jù)
                binder_free_buffer(bs, txn.transaction_data.data.ptr.buffer);
            } else {
                // 如果不是 TF_ONE_WAY 處理,給 binder 驅(qū)動(dòng)回復(fù)數(shù)據(jù)
                binder_send_reply(bs, &reply, txn.transaction_data.data.ptr.buffer, res);
            }
        }
        break;
    }
    }
}

binder_transaction_data 的結(jié)構(gòu)如下,其表明了 transcation 傳輸?shù)木唧w語(yǔ)義,語(yǔ)義碼記錄在 code 中,不同語(yǔ)義碼攜帶的數(shù)據(jù)是不同的,這些數(shù)據(jù)由 data 指定。

struct binder_transaction_data {
    union {
        // binder_ref 該成員指明發(fā)送方的 Binder 實(shí)體引用
        __u32 handle;
        // binder_node 的內(nèi)存地址,當(dāng)數(shù)據(jù)到達(dá)接收方時(shí),驅(qū)動(dòng)已將該成員修改成 Binder 實(shí)體
        binder_uintptr_t ptr;
    } target;
    // BBinder 指針, 發(fā)送方忽略該成員;接收方收到數(shù)據(jù)包時(shí),該成員存放的是創(chuàng)建 Binder 實(shí)體時(shí)由該接收方自定義的任意數(shù)值,做為與 Binder 指針相關(guān)的額外信息存放在驅(qū)動(dòng)中
    binder_uintptr_t cookie;
    // RPC 代碼,代表 Client 與 Server 雙方約定的命令碼,比如 ADD_SERVICE_TRANSACTION
    __u32 code;
    // 標(biāo)志位,比如 TF_ONE_WAY 代表異步,即不等待 Server 端回復(fù)
    __u32 flags;
    // 發(fā)送端進(jìn)程的 pid
    pid_t sender_pid;   
    // 發(fā)送端進(jìn)程的 uid
    uid_t sender_euid;  
    // data 數(shù)據(jù)的總大小
    binder_size_t data_size;    
    // IPC 對(duì)象的大小
    binder_size_t offsets_size; 
    // RPC數(shù)據(jù)
    union {
        struct {
            // 數(shù)據(jù)區(qū)起始地址
            binder_uintptr_t buffer;
            // 數(shù)據(jù)區(qū) IPC 對(duì)象偏移量
            binder_uintptr_t offsets;
        } ptr;
        __u8 buf[8]; 
    } data;
}

在解析完 binder_transaction_data 的具體語(yǔ)義后,會(huì)調(diào)用前面?zhèn)鹘o binder_loop()svcmgr_handler(),其實(shí)就是 switch case 語(yǔ)義碼做不同的事情。

// frameworks/native/cmds/servicemanager/service_manager.c

int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data_secctx *txn_secctx,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    size_t len;
    uint32_t handle;
    uint32_t strict_policy;
    int allow_isolated;
    uint32_t dumpsys_priority;
    // 獲取 transaction_data
    struct binder_transaction_data *txn = &txn_secctx->transaction_data;

    strict_policy = bio_get_uint32(msg);
    bio_get_uint32(msg);  // Ignore worksource header.
    s = bio_get_string16(msg, &len);

    if ((len != (sizeof(svcmgr_id) / 2)) ||
        memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {
        fprintf(stderr,"invalid id %s\n", str8(s, len));
        return -1;
    }
    // 根據(jù)不同的 code 做不同的事情
    switch(txn->code) {
    // 查詢服務(wù)
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
        s = bio_get_string16(msg, &len);
        handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid,
                                 (const char*) txn_secctx->secctx);
        if (!handle)
            break;
        bio_put_ref(reply, handle);
        return 0;
    // 注冊(cè)服務(wù)
    case SVC_MGR_ADD_SERVICE:
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
        handle = bio_get_ref(msg);
        allow_isolated = bio_get_uint32(msg) ? 1 : 0;
        dumpsys_priority = bio_get_uint32(msg);
        if (do_add_service(bs, s, len, handle, txn->sender_euid, allow_isolated, dumpsys_priority,
                           txn->sender_pid, (const char*) txn_secctx->secctx))
            return -1;
        break;
    // 獲取服務(wù)列表
    case SVC_MGR_LIST_SERVICES: {
        uint32_t n = bio_get_uint32(msg);
        uint32_t req_dumpsys_priority = bio_get_uint32(msg);

        if (!svc_can_list(txn->sender_pid, (const char*) txn_secctx->secctx, txn->sender_euid)) {
            ALOGE("list_service() uid=%d - PERMISSION DENIED\n",
                    txn->sender_euid);
            return -1;
        }
        si = svclist;
        while (si) {
            if (si->dumpsys_priority & req_dumpsys_priority) {
                if (n == 0) break;
                n--;
            }
            si = si->next;
        }
        if (si) {
            bio_put_string16(reply, si->name);
            return 0;
        }
        return -1;
    }
    default:
        ALOGE("unknown code %d\n", txn->code);
        return -1;
    }

    bio_put_uint32(reply, 0);
    return 0;
}

ServiceManager 的功能其實(shí)很簡(jiǎn)單:

  • 接收到 SVC_MGR_GET_SERVICESVC_MGR_CHECK_SERVICE 時(shí)調(diào)用 do_find_service() 查找服務(wù)
  • 接收到 SVC_MGR_ADD_SERVICE 時(shí)調(diào)用 do_add_service() 注冊(cè)服務(wù)
  • 接收到 SVC_MGR_LIST_SERVICES 時(shí)獲取所有服務(wù)名稱

至此 ServiceManager 就分析完了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開(kāi)了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    余生動(dòng)聽(tīng)閱讀 10,798評(píng)論 0 11
  • 彩排完,天已黑
    劉凱書(shū)法閱讀 4,452評(píng)論 1 3
  • 沒(méi)事就多看看書(shū),因?yàn)楦褂性?shī)書(shū)氣自華,讀書(shū)萬(wàn)卷始通神。沒(méi)事就多出去旅游,別因?yàn)闆](méi)錢(qián)而找借口,因?yàn)橹灰闶〕詢€用,來(lái)...
    向陽(yáng)之心閱讀 4,964評(píng)論 3 11
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了,難過(guò)就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,426評(píng)論 2 7

友情鏈接更多精彩內(nèi)容