Android7.0 Vold 进程工作机制分析之整体流程

Android7.0 Vold 进程工作机制分析之整体流程

一、Vold简介

Vold是Volume Daemon的缩写,负责管理和控制Android平台外部存储设备,包括SD插拨、挂载、卸载、格式化等。它是通过init进程解析init.rc脚本所启动的进程.它处于Native层.

二、基础架构

这里引用Gityuan博客的一张图。



SystermServer进程和Vold进程是通过Socket进行通信的,Vold进程和Kernel是通过Netlink 进行通信的,Netlink 是一种特殊的Socket。

相关介绍:Netlink是linux提供的用于内核和用户态进程之间的通信方式,也能用于用户空间的两个进程通信,通过这个机制,位于用户空间的进程,可接收来自Kernel的一些信息(例如Vold中用到的USB或SD的插拔消息),同时应用层也可通过Netlink向Kernel发送一些控制命令。



先大体的简单介绍一下流程:

1.由SystemServer进程发起挂载/卸载请求

运行在SystemServer进程的MountService通过NativeDaemonConnector给CommandListener发送请求,CommandListener再通知VolumeManager进行实际操作

2.由Kernel发起挂载/卸载请求

Kernel通过Netlink发送请求(传递uevent)给NetlinkManager,NetlinkManager通过内部的线程NetlinkHandler交给VolumeManager进行实际操作,然后VolumeManager通过CommandListener通知MountService

在分别仔细介绍着两个流程之前,了解下Vold进程的启动流程,先把握整体流程再细化。

三、Vold进程启动流程

Vold进程代码路径:system/vold/

主要类的路径(方便查阅)

system/vold/目录下

main.cpp————————————system/vold/main.cpp

NetlinkManager.cpp———————–system/vold/NetlinkManager.cpp

NetlinkHandler.cpp————————system/vold/NetlinkHandler.cpp

VoldCommand.cpp————————system/vold/VoldCommand.cpp

VolumeBase.cpp—————————system/vold/VolumeBase.cpp

VolumeManager.cpp———————–system/vold/VolumeManager.cpp



system/core/libsysutils/src/目录下

SocketListener.cpp————————–system/core/libsysutils/src/SocketListener.cpp

NetlinkListener.cpp————————–system/core/libsysutils/src/NetlinkListener.cpp

FrameworkListener.cpp——————–system/core/libsysutils/src/FrameworkListener.cpp

FrameworkCommand.cpp——————system/core/libsysutils/src//FrameworkCommand.cpp



system/core/include/sysutils/目录下

对应着system/core/libsysutils/src/目录的h文件

贴上我绘制的时序图(缩放浏览器可以放大查看或者在新标签页打开)

分步骤给大家详细介绍下:

1.main()

Vold进程代码位于system/vold/目录下,从main.cpp开始,后续的截图我只截取主要的代码

在main方法里,主要做以下几件事情

初始化VolumeManager ,CommandListener ,NetlinkManager 三个类的实例

给VolumeManager 和NetlinkManager 设置CommandListener 实例,用作后续监听两个Socket,用得是设计模式中的Command(命令)模式

启动VolumeManager ,CommandListener ,NetlinkManager

解析Vold的配置文件fstab

做一次冷启动

int main(int argc, char** argv) {
    ......

    VolumeManager *vm;
    CommandListener *cl;
    NetlinkManager *nm;

    //创建文件夹/dev/block/vold
    mkdir("/dev/block/vold", 0755);

    //用于cryptfs检查,并mount加密的文件系统
    klog_set_level(6);

    //获取VolumeManager的单例
    if (!(vm = VolumeManager::Instance())) {
        LOG(ERROR) << "Unable to create VolumeManager";
        exit(1);
    }
    //获取NetlinkManager的单例
    if (!(nm = NetlinkManager::Instance())) {
        LOG(ERROR) << "Unable to create NetlinkManager";
        exit(1);
    }

    //实例化
    cl = new CommandListener();

    //设置socket监听对象
    vm->setBroadcaster((SocketListener *) cl);
    nm->setBroadcaster((SocketListener *) cl);

    //启动VolumeManager
    if (vm->start()) {
        PLOG(ERROR) << "Unable to start VolumeManager";
        exit(1);
    }

    //解析Vold的配置文件fstab,初始化VolumeManager
    if (process_config(vm)) {
        PLOG(ERROR) << "Error reading configuration... continuing anyways";
    }

    //启动NetlinkManager
    if (nm->start()) {
        PLOG(ERROR) << "Unable to start NetlinkManager";
        exit(1);
    }
    // 冷启动,vold错过了一些uevent,重新触发。向/sys/block的uevent文件写入”add\n” 字符触发内核发送Uevent消息,相当执行了一次热插拔。
    coldboot("/sys/block");

    //启动CommandListener
    if (cl->startListener()) {
        PLOG(ERROR) << "Unable to start CommandListener";
        exit(1);
    }
    ......
}

如果vold.fstab解析无误,VolueManager将创建具体的Volume,若vold.fstab解析不存在或者打开失败,Vold将会读取Linux内核中的参数,此时如果参数中存在SDCARD(也就是SD的默认路径),VolumeManager则会创建AutoVolume,如果不存在这个默认路径那么就不会创建。

它的格式对应如下:

type———————–挂载命令

lable———————–标签

mount_point ————挂载点

part ———————–第几个分区

sysfs_path—————设备的sysfs paths

sysfs_path可以有多个 part指定分区个数,如果是auto没有分区



coldboot方法会调用do_coldboot方法,往/sys/block目录写入add\n事件。

static void do_coldboot(DIR *d, int lvl) {
    struct dirent *de;
    int dfd, fd;

    dfd = dirfd(d);

    fd = openat(dfd, "uevent", O_WRONLY | O_CLOEXEC);
    if(fd >= 0) {
        //写入add\n事件
        write(fd, "add\n", 4);
        close(fd);
    }

2.new CommandListener()

前面有讲过,CommandListener是用来监听Socket的,监听Vold与Framework层的进程通信。它的关系图如下:

它继承自FrameworkListener,FrameworkListener继承自SocketListener.

路径:

CommandListener.cpp———————–system/vold/CommandListener.cpp

这一步创建CommandListener的实例,则构造方法被调用

CommandListener::CommandListener() :FrameworkListener("vold", true) {
    //注册多条指令
    registerCmd(new DumpCmd());
    registerCmd(new VolumeCmd());
    registerCmd(new AsecCmd());
    registerCmd(new ObbCmd());
    registerCmd(new StorageCmd());
    registerCmd(new FstrimCmd());
    registerCmd(new AppFuseCmd());
}

调用registerCmd方法,注册一些指令,重写的是父类的registerCmd方法

void FrameworkListener::registerCmd(FrameworkCommand *cmd) {
    //添加元素
    mCommands->push_back(cmd);
}

会把指令添加到mCommands 中,mCommands 是FrameworkCommandCollection的实例,在FrameworkListener.h文件中声明的

class FrameworkListener : public SocketListener {
    ......
private:
    FrameworkCommandCollection *mCommands;
    ......
    }

这里附上一张NetlinkManager家族的简单类图,帮助后续理解

3.vm->start()

启动VolumeManager

VolumeManager模块负责管理所有挂载的设备节点以及相关操作的实际执行,

路径:

VolumeManager.cpp———————–system/vold/VolumeManager.cpp

int VolumeManager::start() {

    //卸载所有设备
    unmountAll();

    //智能指针创建一个VolumeBase实例
    mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>(
            new android::vold::EmulatedVolume("/data/media"));
    //调用create()方法
    mInternalEmulated->create();

    return 0;
}

①卸载所有设备

②创建一个VolumeBase实例

③调用VolumeBase的create方法

这里附上一张VolumeManager家族的简单类图,帮助后续理解

看下第③个步骤

3.1 mInternalEmulated->create()

status_t VolumeBase::create() {

    mCreated = true;
    status_t res = doCreate();
    //向VolumeManager发送VolumeCreated命令
    notifyEvent(ResponseCode::VolumeCreated,StringPrintf("%d \"%s\" \"%s\"", mType, mDiskId.c_str(), mPartGuid.c_str()));
    //设置已卸载状态
    setState(State::kUnmounted);
    return res;
}

向VolumeManager发送了VolumeCreated的消息,然后设置状态为已卸载.

这个notifyEvent的实现如下

3.2 notifyEvent

void VolumeBase::notifyEvent(int event, const std::string& value) {
    if (mSilent) return;
    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event,StringPrintf("%s %s", getId().c_str(), value.c_str()).c_str(), false);
}

获取单例VolumeManager对象,然后获取到SocketListener对象调用sendBroadcast方法

sendBroadcast方法的实现如下

在safelist列表中添加SocketClient,然后调用sendMsg方法

void SocketListener::sendBroadcast(int code, const char *msg, bool addErrno) {
    SocketClientCollection safeList;

    //首先添加所有活动的SockClient到安全列表中
    safeList.clear();

    for (i = mClients->begin(); i != mClients->end(); ++i) {
        SocketClient* c = *i;
        c->incRef();
        //添加
        safeList.push_back(c);
    }

    while (!safeList.empty()) {
        /* Pop the first item from the list */
        i = safeList.begin();
        SocketClient* c = *i;
        safeList.erase(i);
        //调用SockClient的sendMSg方法发送消息
        if (c->sendMsg(code, msg, addErrno, false)) {
            SLOGW("Error sending broadcast (%s)", strerror(errno));
        }
        c->decRef();
    }
}

SockClient

路径:

SockClient.cpp————————system/core/libsysutils/src/SockClient.cpp

调用sendMsg方法经过层层跳转,到sendDataLockedv方法中,往Socket中写入信息

3.3 sendDataLockedv

int SocketClient::sendDataLockedv(struct iovec *iov, int iovcnt) {
    ......
    for (;;) {
        ssize_t rc = TEMP_FAILURE_RETRY(
            writev(mSocket, iov + current, iovcnt - current));
    ......
}

写入到Socket之后,SystemServer中的MountService会收到,后续再细讲.

4.nm->start()

这一步启动NetlinkManager

NetlinkManager

路径:

NetlinkManager.cpp———————–system/vold/NetlinkManager.cpp

NetlinkManager模块接收从Kernel通过Netlink机制发送过来的Uevent消息,解析转换成NetlinkEvent对象,再将此NetlinkEvent对象传递给VolumeManager处理。

start方法如下

int NetlinkManager::start() {
    ......
    //创建Socket
    if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC,
            NETLINK_KOBJECT_UEVENT)) < 0) {
        SLOGE("Unable to create uevent socket: %s", strerror(errno));
        return -1;
    }
    //设置Socket的SO_RCVBUFFORCE大小
    if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {
        SLOGE("Unable to set uevent socket SO_RCVBUFFORCE option: %s", strerror(errno));
        goto out;
    }
    //设置Socket的SO_PASSCRED大小
    if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
        SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));
        goto out;
    }
    //绑定Socket
    if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {
        SLOGE("Unable to bind uevent socket: %s", strerror(errno));
        goto out;
    }

    //创建一个NetlinkHandler对象,并把创建好的Socket句柄传给它。
    mHandler = new NetlinkHandler(mSock);
    //启动NetlinkHandler
    if (mHandler->start()) {
        SLOGE("Unable to start NetlinkHandler: %s", strerror(errno));
        goto out;
    }
    return 0;
    ......

}

①创建Socket,为PF_NETLINK类型

②设置Socket的SO_RCVBUFFORCE(接受缓存区)小

③设置Socket的SO_PASSCRED大小

④创建一个NetlinkHandler对象,并启动它

主要看第④步

4.1 mHandler->start()

构造函数传入了mSock,然后调用了NetlinkHandler的start方法

看一下它的实现

路径:

NetlinkHandler.cpp———————–system/vold/NetlinkHandler.cpp

NetlinkHandler::NetlinkHandler(int listenerSocket) :NetlinkListener(listenerSocket) {
}

int NetlinkHandler::start() {
    //startListener由SocketListener实现
    return this->startListener();
}

void NetlinkHandler::onEvent(NetlinkEvent *evt) {
    VolumeManager *vm = VolumeManager::Instance();
    const char *subsys = evt->getSubsystem();
    // VolumeManager 调用handleBlockEvent处理事件
    if (!strcmp(subsys, "block")) {
        vm->handleBlockEvent(evt);
    }
}

NetlinkHandler继承自NetlinkListener,NetlinkListener继承自SocketListener,三者关系如下

4.2 startListener

会在父类SocketListener中实现,调用startListener方法

路径:

SocketListener.cpp————————–system/core/libsysutils/src/SocketListener.cpp

SocketListener

int SocketListener::startListener(int backlog) {
     ......
    //创建线程执行函数threadStart
    if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {
        SLOGE("pthread_create (%s)", strerror(errno));
        return -1;
    }
    return 0;
}

会开启一个线程,那么继续看threadStart方法

void *SocketListener::threadStart(void *obj) {
    SocketListener *me = reinterpret_cast<SocketListener *>(obj);
    //调用runListener方法
    me->runListener();
    pthread_exit(NULL);
    return NULL;
}

调用runListener

4.3 runListener

void SocketListener::runListener() {

    SocketClientCollection pendingList;
        ......
        while (!pendingList.empty()) {

            it = pendingList.begin();
            SocketClient* c = *it;
            pendingList.erase(it);
             //onDataAvailable方法处理有数据发送的socket
            if (!onDataAvailable(c)) {
                release(c, false);
            }
            c->decRef();
        }
    }
}

会调用onDataAvailable方法,改方法由子类NetlinkListener和FrameWorkListener实现,所以这里要分两条线4.4和4.5。

4.4 onDataAvailable

由子类FrameWorkListener实现这个方法

bool FrameworkListener::onDataAvailable(SocketClient *c) {
    char buffer[CMD_BUF_SIZE];
    int len;
    //读取socket消息
    len = TEMP_FAILURE_RETRY(read(c->getSocket(), buffer, sizeof(buffer)));
    .....
    int i;
    for (i = 0; i < len; i++) {
        if (buffer[i] == ‘\0‘) {
            //根据消息内容 派发命令
            dispatchCommand(c, buffer + offset);
            offset = i + 1;
        }
    }

    return true;
}

在onDataAvailable方法里会先读取Socket消息,然后分发命令

4.4.1 dispatchCommand

void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) {
     ......
    //执行对应的消息
    for (i = mCommands->begin(); i != mCommands->end(); ++i) {
        FrameworkCommand *c = *i;
        //匹配命令
        if (!strcmp(argv[0], c->getCommand())) {
            //执行命令
            if (c->runCommand(cli, argc, argv)) {
                SLOGW("Handler ‘%s‘ error (%s)", c->getCommand(), strerror(errno));
            }
            goto out;
        }
    }
     ......
}

会调用FrameworkCommand 的runCommand方法,之前在CommandListener的构造方法里注册的这些指令,就是FrameWorkCommand类型,如下

FrameworkListener.cpp

void FrameworkListener::registerCmd(FrameworkCommand *cmd) {
    //添加元素
    mCommands->push_back(cmd);
}

CommandListener.cpp

CommandListener::CommandListener() :FrameworkListener("vold", true) {
    //注册多条指令
    registerCmd(new DumpCmd());
    registerCmd(new VolumeCmd());
    registerCmd(new AsecCmd());
    registerCmd(new ObbCmd());
    registerCmd(new StorageCmd());
    registerCmd(new FstrimCmd());
    registerCmd(new AppFuseCmd());
}

这里以其中一个指令为VolumeCmd例,会进入到VolumeCmd的runCommand方法

4.4.2 CL.runCommand

CommandListener.cpp

int CommandListener::VolumeCmd::runCommand(SocketClient *cli, int argc, char **argv) {

    ......

    } else if (cmd == "mount" && argc > 2) {
        // mount [volId] [flags] [user]
        std::string id(argv[2]);
        auto vol = vm->findVolume(id);
        if (vol == nullptr) {
            return cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume", false);
        }

        int mountFlags = (argc > 3) ? atoi(argv[3]) : 0;
        userid_t mountUserId = (argc > 4) ? atoi(argv[4]) : -1;

        vol->setMountFlags(mountFlags);
        vol->setMountUserId(mountUserId);
        //执行真正的挂载操作
        int res = vol->mount();
        if (mountFlags & android::vold::VolumeBase::MountFlags::kPrimary) {
            vm->setPrimary(vol);
        }
        //发送应答消息给MountService
        return sendGenericOkFail(cli, res);

    } else if (cmd == "unmount" && argc > 2) {
        // unmount [volId]
        std::string id(argv[2]);
        auto vol = vm->findVolume(id);
        if (vol == nullptr) {
            return cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume", false);
        }

        return sendGenericOkFail(cli, vol->unmount());
        ......
    }
}

会根据cmd的不同做相应的处理,在”cmd=mount”操作中,就有

int res = vol->mount();

4.4.3 vol->mount()

vol是VolumeBase的实例,VolumeBase的mount方法由具体的子类EmulatedVolume、PublicVolume、PrivateVolume等实现

执行操作之后会发送应答消息给MountService,这个后续再细讲。

这条线结束了。下面介绍另一条线。回到4.3 runListener,往下走到4.5 onDataAvailable

由子类NetlinkListener实现

4.5 onDataAvailable()

bool NetlinkListener::onDataAvailable(SocketClient *cli)
{
    int socket = cli->getSocket();
    ssize_t count;
    uid_t uid = -1;
    ......
    NetlinkEvent *evt = new NetlinkEvent();
    //解析获得NetlinkEvent实例
    if (evt->decode(mBuffer, count, mFormat)) {
        //传入NetlinkEvent实例
        onEvent(evt);
    }
    ......
}

NetlinkListener没有实现这个方法,由子类NetlinkHandler实现

4.5.1 onEvent

void NetlinkHandler::onEvent(NetlinkEvent *evt) {
    VolumeManager *vm = VolumeManager::Instance();
    const char *subsys = evt->getSubsystem();

    if (!subsys) {
        SLOGW("No subsystem found in netlink event");
        return;
    }

    if (!strcmp(subsys, "block")) {
        //调用VolumeManager 的handleBlockEvent方法
        vm->handleBlockEvent(evt);
    }
}

获取VolumeManager 单例,调用handleBlockEvent方法

4.5.2 vm->handleBlockEvent

void VolumeManager::handleBlockEvent(NetlinkEvent *evt) {

    switch (evt->getAction()) {
    case NetlinkEvent::Action::kAdd: {
        for (auto source : mDiskSources) {
            if (source->matches(eventPath)) {
                ......
                auto disk = new android::vold::Disk(eventPath, device,source->getNickname(), flags);
                //调用disk 的create方法
                disk->create();
                mDisks.push_back(std::shared_ptr<android::vold::Disk>(disk));
                break;
            }
        }
        break;
    }
    case NetlinkEvent::Action::kChange: {
        ......
        break;
    }
    case NetlinkEvent::Action::kRemove: {
        ......
        break;
    }
    ......
    }
}

根据NetlinkEvent 的不同Action值做相应处理,有add,change,remove三个值。在add里调用了disk->create()

4.5.3 disk->create()

路径:

disk.cpp———————–system/vold/disk.cpp

status_t Disk::create() {
    CHECK(!mCreated);
    mCreated = true;
    //调用notifyEvent方法
    notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags));
    readMetadata();
    readPartitions();
    return OK;
}

在这一步会调用notifyEvent方法通知SockListener.

void Disk::notifyEvent(int event) {
    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event,getId().c_str(), false);
}

到这一步骤,会发现又回到了3.2 notifyEvent步骤了,之后就跟着3.2继续走了,这里就不重复介绍了.

四、Vold进程总结

关于Vold的整体流程的讲完了,可以跟着我绘制的时序图跟着步骤走,来回多看几次,注意图文结合.

后面会针对

1.由SystemServer进程发起挂载/卸载请求

2.由Kernel发起挂载/卸载请求

这两种情况做更具体的分析,分析源码切记:先整体后局部,不然就没有头绪了,只见树木不见森林.

PS:水平有限,有不对的地方欢迎一起探讨

时间: 2024-10-01 00:23:16

Android7.0 Vold 进程工作机制分析之整体流程的相关文章

Android7.0 Phone应用源码分析(三) phone拒接流程分析

接上篇博文:Android7.0 Phone应用源码分析(二) phone来电流程分析 今天我们再来分析下Android7.0 的phone的拒接流程 下面先来看一下拒接电话流程时序图 步骤1:滑动按钮到拒接图标,会调用到AnswerFragment的onDecline方法 com.android.incallui.AnswerFragment public void onDecline(Context context) { getPresenter().onDecline(context);

Android7.0 Phone应用源码分析(二) phone来电流程分析

接上篇博文:Android7.0 Phone应用源码分析(一) phone拨号流程分析 今天我们再来分析下Android7.0 的phone的来电流程 1.1TelephonyFramework 当有来电通知时,首先接收到消息的是Modem层,然后Medoem再上传给RIL层,RIL进程通过sokcet将消息发送给RILJ(framework层的RIL),同样进入RILJ的processResponse方法,根据上一章节去电流程的分析得知,来电属于UnSolicited消息,事件ID是 RIL_

[网络课摘抄]2.CKPT进程工作机制

CKPT进程工作示意图 2.CKPT进程工作机制 检查点进程被触发的条件为: a> 当发生日志组切换时: b>  用户提交了事务时(commit): c>  Redo log buffer容量达到总容量的1/3或1M时. d> 手动alter system checkpoint 的时候. e>  系统正常关闭时. f>  其他(如alter tablespace .. begin backup/end backup) 当一个检查点进程发生时(图中的2),首先系统会记录检

Java IO工作机制分析

Java的IO类都在java.io包下,这些类大致可分为以下4种: 基于字节操作的 I/O 接口:InputStream 和 OutputStream 基于字符操作的 I/O 接口:Writer 和 Reader 基于磁盘操作的 I/O 接口:File 基于网络操作的 I/O 接口:Socket 1 IO类库的基本结构 1.1 基于字节操作的IO接口 基于字节操作的IO接口分别是InputStream和OutputStream,InputStream的类结构图如下所示: 同InputStream

Storm进程通信机制分析

本文主要分析storm的worker进程间消息传递机制,消息的接收和处理的大概流程见下图 在Storm中,worker进程内部的thread通信与worker进程间的通信有一些差别,worker间的通信经常需要通过网络跨节点进行,Storm使用ZeroMQ或Netty(0.9以后默认使用)作为进程间通信的消息框架.worker进程内部通信或在同一个节点的不同worker的thread通信使用LMAX Disruptor来完成. 对于worker进程来说,为了管理流入和传出的消息,每个worker

Android7.0 Phone应用源码分析(一) phone拨号流程分析

1.1 dialer拨号 拨号盘点击拨号DialpadFragment的onClick方法会被调用 public void onClick(View view) { int resId = view.getId(); if (resId == R.id.dialpad_floating_action_button) { view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); handleDialButtonPressed(

Map/Reduce 工作机制分析 --- 错误处理机制

前言 对于Hadoop集群来说,节点损坏是非常常见的现象. 而Hadoop一个很大的特点就是某个节点的损坏,不会影响到整个分布式任务的运行. 下面就来分析Hadoop平台是如何做到的. 硬件故障 硬件故障可以分为两种 - JobTracker节点损坏和TaskTracker节点损坏. 1. JobTracker节点损坏 这是Hadoop集群中最为严重的错误. 出现了这种错误,那就只能重新选择JobTracker节点,而在选择期,所有的任务都必须停掉,而且当前已经完成了的任务也必须通通重来. 2.

Map/Reduce 工作机制分析 --- 数据的流向分析

前言 在MapReduce程序中,待处理的数据最开始是放在HDFS上的,这点无异议. 接下来,数据被会被送往一个个Map节点中去,这也无异议. 下面问题来了:数据在被Map节点处理完后,再何去何从呢? 这就是本文探讨的话题. Shuffle 在Map进行完计算后,将会让数据经过一个名为Shuffle的过程交给Reduce节点: 然后Reduce节点在收到了数据并完成了自己的计算后,会将结果输出到Hdfs. 那么,什么是Shuffle阶段,它具体做什么事情? 需要知道,这可是Hadoop最为核心的

Map/Reduce 工作机制分析 --- 作业的执行流程

前言 从运行我们的 Map/Reduce 程序,到结果的提交,Hadoop 平台其实做了很多事情. 那么 Hadoop 平台到底做了什么事情,让 Map/Reduce 程序可以如此 "轻易" 地实现分布式运行? Map/Reduce 任务执行总流程 经过之前的学习,我们已经知道一个 Map/Reduce 作业的总流程为: 代码编写  -->  作业配置  -->  作业提交  -->  Map任务的分配和执行  -->  处理中间结果(Shuffle)  --&