关于io

    技术2022-06-09  65

    关于io_request

    相关module

    涉及到kernel/io, kernel/user, kernel/application_master, stdlib/slave.

    Erlang中的标准io接口

    stdlib/io module, 为erlang中的标准的io接口模块,其io操作基于process实现, 如对文件, 标准输入输出等操作,都可以使用此module进行处理(对于文件,file:open/2, 默认是使用io模块进行操作,除非指定raw选项).

     

    使用io module具有一些好处:我们可以使用统一的接口对实现io操作; 其他的process也可以访问被打开的io device; 在分布式环境中,可以进行基于Process Message机制的交互. 至于缺点,毫无疑问,就是可能导致性能下降, 比如对于file的操作,使用raw模式会更快.

    关于group leader

    一组process,可以组成一个group, 请注意这里的group和kernel中的 pg2, global, global_groupe等没有任何关系.

    group leader可以理解成process的一个属性.我们可以通过erlang:process_info/1,可以查看process的group_leader 信息. group_leader对应的value为某个process pid.

     

    每当我们spawn一个新的process时, 其都会继承parent的group_leader属性.

     

    估计很多人会有疑问,group_leader有何作用呢?

     

    process的io module相关的操作, 会转交给group_leader进行处理. 所以group_leader类似一个router, 其接受io module的 io_request请求,然后转发给相应的module进行处理.

     

    对于io这方面的操作,很多语言可能是在运行时的内部进行实现支持. erlang在运行时之上又分离出group_leader这一层, 目的就是在分布式系统中, 我们可以通过重新设置process的group_leader,从而达到重定向其io流的方向. 这个概念似乎和*unix中的"|", 重定向有点类似.

     

    我们可以使用group_leader,实现分布式系统中, 节点io的重定向, 这样便于我们调试和记录一些信息.

     

    erlang emulator启动时,init作为第一个process(通常编号为<0.0.0>), 是所有process的group_leader.

     

    kenerl lib中user模块, 负责处理具体的io_request, 其可以在erlang shell输出数据, 可以从erlang shell读取数据,因此对于我们本文所讲述的io_request, user是最顶层的group_leader.

    application与group_leader

    application创建的时候, 其默认的group_leader为user process, 在application对应的application master中,其设置group leader 为self, 但是app master同时保存原有的group leader即user.

     

    application中所有的process的group leader都指向其的application master. 任意一个process的io request信息都会发送给app master.

     

    注意,前面我们提过, app同时保存了旧的group leader, 在app master的loop中, 我们看到处理代码如下:

    IoReq when element(1, IoReq) =:= io_request -> State#state.gleader ! IoReq, init_loop(Pid, Tag, State, Type); ....

    其将io request消息,简单的发送给了old group leader, 也就是user. 这里这么做,我猜测可能是为了以后的某些功能.

    使用io模块

    我们经常调用io:format/1,2,3, 用来显示一些调试及提示信息, 那这个命令执行过程是怎么样的呢?

     

    如果使用io:fromat/1,2, 没有指定io device, 那么default_output即是调用此函数process的group_leader(). 如果此process属于某个application,那么依据前面的知识,我们可以知道,消息的路线如下:

     

    io:fromat/1,2 -> app master (group_leader) -> user

     

    我们也可以使用io:format/3, 通过指定io device为user, 指定目标device. 那么消息的路线如下:

     

    io:format(user, Msg, D) -> user

    slave节点的io request

    stdlib中的slave module,可以用来启动slave节点, slave module说明中有下面的描述:

     

    All TTY output produced at the slave will be sent back to the master node. File I/O is done via the master.

     

    这里是如何实现的呢?

     

    比如我们的master节点通过ssh启动slave节点, 其参数中,有一个 -master 选项,其设置为master node()(具体参看stdlib/slave.erl),  当远程的slave节点的erlang emulator启动时, user_sup module判断 -master 如果设置, 则不会创建一个新的user process, 其获取master的user pid, 然后注册成本地user name指向master的user pid.

     

    具体代码如下(user_sup.erl):

     

    case get_user() of ... {master, Master} -> Pid = start_slave(Master), {ok, Pid, Pid}; ... start_slave(Master) -> case rpc:call(Master, erlang, whereis, [user]) of User when is_pid(User) -> spawn(?MODULE, relay, [User]); _ -> ... end. relay(Pid) -> register(user, self()), relay1(Pid). relay1(Pid) -> receive X -> Pid ! X, relay1(Pid) end

    在relay1的loop中,任何io request及其他消息,只是简单的交给远程的master进行处理.

    一个简单的例子

    东拉西扯,说了很多东西,可能比较模糊. 让我们举一个简单的例子, 希望可以让你的思路更加清晰.

     

    我们在本地,启动两个node foo, bar, 将Node bar的group leader设置为foo的user, 看看效果:

     

    foo:

    erl -sname foo (foo@cheng)1>

     

    bar:

    erl -sname bar (bar@cheng)1> net_kernel:connect_node('foo@cheng'). true (bar@cheng)2> [Foo] = nodes(). ['foo@cheng'] (bar@cheng)3> User = rpc:call(Foo, erlang, whereis, [user]). <5268.28.0> (bar@cheng)4> group_leader(User, self()).

     

    此时,在foo的shell发生了变化: foo:

     

    (foo@cheng)1> true

     

    这个就是bar节点上调用group_leader/2的返回值:true.

     

    好了, 现在bar的erlang shell 所在process的group leader已经设置为bar的user process. 让我们调用io module输出一些信息:

     

    bar:

    (bar@cheng)5> io:format("hello, i'm ~p~n", [self()]).

     

    foo显示信息:

    (foo@cheng)1> hello, i'm <5230.35.0> (foo@cheng)1> ok

     

    其中<5230.35.0>为bar中shell process的pid. 下面的ok,是在bar中调用io:format/2后的输出. 我们在bar中的标准输出,现在已经传输到foo中.

     

    那么有没有什么方法,可以让输出显示在bar中呢? 当然可以!

     

    bar:

    (bar@cheng)6> io:format(user, "please show in the bar~n", []). please show in the bar ok

     

    我们通过指定io device为local user, 来使io request显示在本地.

    小提示

    可以通过设置group leader来实现io request重定向, 在记录日志,调试系统方面具有一定的作用.

     

    一点问题:io reqeust采用request-reponse的方式进行交互, 发送的消息随后会等待应答, 比如调用 io:format返回的ok, 即是应答.

    参考

    http://mryufeng.iteye.com/blog/271706

     

     

    转载:http://erlangdisplay.iteye.com/blog/336005


    最新回复(0)