0. 用C写的内联驱动的port driver运行时嵌入到Eralng虚拟机中,就像是Erlang的一部分,而外部接口是在一个独立的操作系统进程中运行的。
内联驱动的优点是高效,缺点是写的不好会连累Erlang虚拟机,把后者崩溃掉。所以,很危险要小心。
一个比较好的参考例子是
bfile,bfile实际上是对c的标准文件操作(如fopen(), fread()等)做了包装。
用erl_ddll:loaded_drivers().可以看到缺省情况下erl提供的常见driver:
> erl_ddll:loaded_drivers().
{ok,["efile","tcp_inet","udp_inet","zlib_drv",
"ram_file_drv","tty_sl","GDAL_drv"]}
写driver时可以参考一下。
注意driver中的函数都是静态的,静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
C语言中定义静态函数的好处:
静态函数会被自动分配在一个一直使用的存储区,直到退出应用程序实例,避免了调用函数时压栈出栈,速度快很多。
关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。 使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
1. Port Driver共享库的装载与卸载
装载(erl_ddll:load(Path,LibName))指定库文件所在目录和库名字(库文件的名字与ErlDrvEntry结构driver_name字段的名字一致),装载时会自动执行我们在ErlDrvEntry结构init字段中指定的函数(如果指定了),我们可以在这个函数中为driver做一些准备工作;
卸载(erl_ddll:unload(Name)时只需指定要卸载的库名,即ErlDrvEntry结构中的driver_name(也是上面erl_ddll:loaded_drivers列出的库名),如果ErlDrvEntry结构体中指定了finish字段的函数,那么卸载时会自动执行此函数,可以用此函数做一些库的清理工作
Port Driver共享库在Erlang运行时中的存在与转载库的进程有关,如果装载库的进程(即调用erl_ddll:load的进程死掉了,这个进程装载的库也会自动被卸载。可以自己验证一下:
erl_ddll:loaded_drivers().
spawn(fun() ->erl_ddll:load(".", 'YOUR_drv'), timer:sleep(5000) end).
erl_ddll:loaded_drivers().
五秒钟后再
erl_ddll:loaded_drivers().
所以,照看好你的装载共享库的进程,不要让它意外退出了。
2. 端口(Port)的打开与关闭
用open_port启动一个端口时会自动执行ErlDrvEntry结构体中start字段指定的函数;
Port关闭时(如调用unlink关闭)是会指定执行ErlDrvEntry 结构体stop字段指定的函数。
在开启port直到关闭的这段作用域内,我们可能需要共享一些数据以方便对port的操作。该函数会返回一个指针,这个指针是一个ErlDrvData数据类型,实际上指向一个用户自定义的结构体,这个ErlDrvData数据(也就是那个指针)又称为port handle,某种意义上这个指针代表被打开的port,因此在driver的其它回调函数中,ErlDrvData数据(也就是那个指针)总是传入的第一个参数。
例如bfile包装了底层对文件的读写操作,底层实际上是以文件句柄的方式操作文件,每开启一个bfile 端口,就是对应着一个文件的打开(得到一个文件句柄),以后通过端口对文件的操作都是通过该句柄进行的,因此bfile的c实现中需要记录打开的对应文件句柄。这个文件句柄可以在端口打开时记录在ErlDrvPort结构中,以后每次对端口操作时就可以从ErlDrvPort结构体中得到port对应的文件句柄了。
在
端口连接进程(即调用open_port打开端口的进程)死掉后,端口会自动关闭。
也可以自己主动关闭端口
unlink(Port),
catch erlang:port_close(Port).
3. 当在Erlang上调用erlang:port_control时,对应的C driver的control函数将被调用,执行的结果通过connect函数的输出参数rbuf传回给erlang emulator,erl_interface中的ei库会将数据编码成erlang的二进制term,因此在erlang一端可以调用binary_to_term将结果转成term。显然,在C driver中为输出而给rbuf分配的内存应该由erlang emulator负责释放。
4. C程序的编译,连接的时候要小心别遗漏某些库,不然编译连接都通过了,库文件也生成了,运行时却出问题;例如,有一次程序中拼写错误调用了一个不存在的函数,但是编译连接都通过,只是在加载的时候才给出错误。
5. C++的内联驱动
DRIVER_INIT应该声明为extern "C" 的:
extern "C" DRIVER_INIT(MY_drv)
更多的例子参考
generic erlang port driver,这个项目提供了一个框架,方便了C/C++的外部端口以及内联驱动程序的编写。
6. 内存泄漏的问题
自己用C写内联驱动程序最可能出现的问题就是内存泄漏了,我在linux下使用了一个比较土的检测方法:用一个脚本每十秒钟检测一下erlang虚拟机的内存,具体是调用了ps -o rss命令进行的(单位是KB),这个命令可能不够精确,但也够用了。检测数据都写入erl_mem.log文本文件中,最后一列即是erlang运行时的内存:
# ERLPID=`pgrep -f 'beam'`; while [ 1 ] ; do MEM=`ps -o rss= -p $ERLPID`; echo -e "`date`\t`date +%s`\t$MEM\t"; sleep 10; done | tee -a erl_mem.log
如果发现内存数量只升不降多半是出现内存泄漏了。
运行这个脚本的机器上不要同时运行两个erlang运行时。
7. 想在C driver里用erl_interface库函数解析二进制数据时出现问题,函数erl_init(NULL, 0)不调用还好,一旦出现,即使没有执行到,控制台上就会大约无穷的:
(no error logger present) error: "erts_poll_wait() failed: einval (22)\n"
真邪门
erl_interface库一般用于外联驱动,或者写C node,为这些程序提供erlang term数据结构的转换支持,这些程序的特点是都独立于Erlang运行,而内联驱动的程序是作为Erlang自身的一部分运行,也许和这个有关,待证实
查官方的例子(在erts/example/目录下)可以看到,写内联驱动的driver进行erlang term的转换时没有用erl_interface,而是使用的ei进行term的编码和解码,ei的抽象程度要比erl_interface低,但是至少提供了将C语言的数据结构转换成对应的Erlang term结构的一个包装。一个生成二元tuple的例子:
ei_x_buff x; // 将用来表示Erlang term的C数据结构
ei_x_new_with_version(&x);
ei_x_encode_tuple_header(&x, 2); //这是一个二元的tuple
ei_x_encode_atom(&x, "ok"); // tuple的第一个元素是一个atom,值为ok
ei_x_encode_string(&x, "abcdef"); // 第二个元素是字符串abcdef
生成了由ei_x_buff 表示的term,
ErlDrvBinary* bin = driver_alloc_binary(x.index);
memcpy(&bin->orig_bytes[0], x.buff, x.index);
从ei_x_buff提取出二进制binary,在erlang上调用binary_to_term即可得到成Erlang term,最后得到tuple
{ok, "abcdef"}
port driver的官方文档是ERTS User‘s Guide的
第6章 How to implement a driver
如果driver中的计算比较耗时,可能会阻塞erlang emulator,别的Erlang进程就不能执行了,这是不可接受的,解决办法是实现一个异步driver,在6.5和6.6节对此有介绍
分享到:
相关推荐
1. **导出内联函数**:在导出内联函数时需要注意,如果目标平台不支持内联,则可能会出现问题。 2. **导入内联函数**:导入内联函数时也需要确保目标环境支持内联机制。 #### 九、总结 内联函数是C++中一种重要的...
- **数据类型兼容性**:在混合使用C/C++和汇编语言时,需要注意数据类型的转换和兼容性问题,特别是对于结构体和数组的操作。 #### 实际应用案例 假设有一个简单的示例,我们需要使用内联汇编来读取一个数组中的...
### ARM GCC 内联汇编参考手册解析 #### 关键知识点概述 ...正确使用内联汇编可以显著提高代码性能,但错误的使用也可能会引入难以调试的问题。因此,在决定是否使用内联汇编时,应该仔细考虑其利弊。
易语言是一种以中文编程为特色的程序设计语言,旨在降低编程技术门槛,让更多人能够掌握编程技能。"易语言置入代码内联...在论坛中与其他开发者交流,可以加快学习进度,解决遇到的问题,共同推动易语言技术的发展。
4. **特定功能实现**:例如,在编写设备驱动程序时,可能需要直接访问硬件资源,这时内联汇编非常有用。 ##### 缺点: 1. **可移植性差**:由于不同的处理器架构有不同的汇编指令集,因此内联汇编代码通常只适用于...
内联汇编是编程语言中一个重要的特性,它允许程序员在高级语言中直接插入汇编代码,以便进行底层性能优化或者解决某些特定问题。在VB.NET(Visual Basic .NET)中,虽然它主要是一个高级面向对象的语言,但通过内联...
在编写内联汇编时,应谨慎使用,因为它可能导致内存管理问题和安全漏洞。理解汇编的副作用和约束是避免这类问题的关键。 总结来说,VC内联汇编是一种强大的工具,它为开发者提供了直接操纵硬件的能力,但也带来了可...
9. **移植性问题**:认识到内联汇编可能会降低代码的可移植性,因此在使用时需要权衡性能提升和代码维护性。 10. **最佳实践**:学习何时和如何避免过度使用内联汇编,以及何时应该考虑使用库函数或已优化的库。 ...
IBM内联汇编指导主要介绍的是如何使用IBM XLC/C++编译器在IBM z系统Linux平台上进行内联汇编编程,以提升应用程序性能。在文章中,作者Anh Tuyen Tran提供了对内联汇编基础知识的深入讲解,并指出了该技术的优势和...
C++内联汇编是一种将汇编语言代码嵌入到C++程序中的技术,它允许开发者在高级语言中直接插入低级操作,以优化特定性能关键的代码段或解决特定平台的问题。本工程通过一系列示例,展示了如何在C++程序中使用内联汇编...
这导致了一些问题,如类型安全缺乏、不能捕获运行时错误、不能进行函数重载等。内联函数则是通过编译器实现的,具备了函数的全部特性,可以进行类型检查、异常处理以及访问控制,同时避免了函数调用的开销。 内联...
1. **确保每个包含该头文件的编译单元都能看到完整的函数体**:由于内联函数的目标是在编译时将函数调用替换为函数体代码,这就意味着每个调用该内联函数的编译单元都需要知道函数体的确切内容。如果将内联函数体...
它通过#define定义一个标识符,然后在源代码中遇到这个标识符时,编译器会将它替换为定义时的内容。例如,#define TABLE_COMP(x) ((x)>0?(x):0) 定义了一个简单的条件判断宏。宏的优点在于它不涉及函数调用的开销,...
内联函数和宏在C++编程语言中是两种常见的...总的来说,内联函数和宏是C++中的两种工具,各有特点,理解它们的差异有助于写出更高效、更安全的代码。学习并掌握这些概念,对于提升编程技能和解决问题的能力至关重要。
2. x86平台转x64平台的挑战:当从x86平台转到x64平台时,需要注意内联汇编的不再支持问题。x64平台下不再支持内联汇编,这给开发者带来了挑战。 3. 解决方案:可以单独写汇编ASM文件,编译生成.OBJ文件,然后将独立...
"内联函数详解" ...内联函数是一种非常有用的函数类型,但是需要正确地使用它,以免出现问题。在编程时,需要注意内联函数的定义格式、编程风格、优缺点、使用注意事项,并且区分内联函数与宏的区别。
在Java中,由于JVM的即时编译(JIT)技术,某些方法可能会在运行时被内联。JIT会分析方法的调用频率和复杂性,决定是否对方法进行内联优化。对于final方法和私有方法,由于它们不会被重写,JVM更倾向于进行内联,...
SQL语句的内联外联经典面试题 SQL语句的内联外联是一种常见的数据库查询技术,它可以根据不同的连接方式将多个表连接起来,提高查询效率和数据整合性。在面试中,SQL语句的内联外联经典面试题是一个非常重要的考察...