`
xitong
  • 浏览: 6193448 次
文章分类
社区版块
存档分类
最新评论

java和c的本质--最重要的是启动

 
阅读更多

java很神秘吗?说什么跨平台,虚拟机之类的。c#很神秘吗?c很神秘吗?操作系统呢?cpu呢?其实这些都不神秘,以前不懂操作系统的时候,看见个多线程就跟看见个神似的,现在呢?linux内核随便看,随便改,不就是进程管理那一堆事嘛,也没有多少代码。学习任何东西的时候,只要静态的框架以及动态的流程搞明白了,都不难的,这就和学英语一样,静态的东西其实就是字母表和单词,动态的东西就是听说读写,动静结合,必有长进。
下面以c和java为例说明其原理,特别说明c和java是如何启动的,想必搞这个之前,每个人的c或者java的功底一定很高了。下面就一步一步地来:
写c程序的时候,一般要写个main:
int main(int argc, char **argv);
写java的时候,一般也要写个main:
public static void main(String argv[]);
在c语言的下面,有加载器,类似的,在java的下面,也有加载器,c的加载器必须脱离c语言编程的机制,但是仍然可以使用c的语法写成,那么启动java的机制也必须脱离java编程的机制(加载器以下可能称为链接器)。
每一个编译好的c程序都会在其elf文件中被加入动态链接器的信息,操作系统启动新的elf文件的时候(和进程没有关系,进程是fork创建的,这里仅仅是加载elf,也就是exec),首先调用链接器的入口,然后连接器中再调用c的main函数(如上),其实仅仅告诉操作系统一个入口点就可以了,随便一个都可以,操作系统会自动调转到那里的,但是一般而言每个操作系统的动态链接程序是固定的,比如在linux上就是ld-linux,这是为了减少数据冗余,和重用组件的思想是一致的,因此一般而言,固定的链接器调用固定的c入口,这个入口就被规定成了main函数,如果你自定义链接器的入口,那么你完全可以调起来入口不是main的c程序。实际上,我们已经在使用这个办法了,那就是加载动态库并且调用动态库的函数,这个意义上,动态库就是一个可以没有main的c程序,ld-linux.so这一类so本质上它们才是真正的程序入口,而我们编写的带有main的c程序可以理解成是ld-linux.so的一个动态库。
java的链接器或者加载器或者称为启动器其实也是一样的道理,只是它比c更上层了,它的启动器不是操作系统直接调用的,而是c语言调用的,可以认为一个带有main函数的c语言写成的程序作为了java的启动器,这个c启动器可以调用别的动态库,在这些本地环境中为java的执行构建了一个虚拟的“执行”环境,这就是java虚拟机,注意,这里“执行”环境很重要,它导致java是跨平台的,操作系统和c库其实也做到了屏蔽下层的作用,然而它们都没有能模拟一个执行环境,仅仅做到了接口兼容而不是二进制兼容,对于操作系统而言,比如linux完全向用户空间暴露了机器指令,因此安装在sparc上的linux和安装在x86上的linux其上的应用程序是不兼容的,c库也是一样的道理。另外就是操作系统本身的系统调用接口的不统一也会导致程序无法即使在相同硬件但是不同操作系统上二进制跨平台。这种局面也许在操作系统和库设计支持,对于跨平台执行没有太大的需求,再者那时的硬件性能普遍很低,增加很多的虚拟层势必会进一步降低性能,第三,那时的人们并没有面对复杂应用的挑战,因此和机器比较亲近,软件工程几乎完全没有被系统研究。
ld-linux.so到底有什么用以及怎么用?它是到ld-2.x.so的软链接,由于几乎每一个正常且正规的程序都使用它,可以说,你把它删除了你的系统就起不来了,除非把磁盘mount到另一个系统上,然后拷贝一个过去,或者自己在别的系统上写一个自定义链接器的程序...(还有一种办法就是安装sash-stand alone shell,它不依赖任何别的库,静态编译的它因此也不依赖ld-linux,因此可以通过内核启动参数init=/sbin/sash来启动到它),然而如果你移动了它导致系统找不到它引起的任何程序无法运行,只要你知道把它搞到了哪里,那就有救,做以下实验:
#mv /lib/ld-2.7.so ./aaa
然后你会发现任何程序都没有办法运行了,此时幸好还有一个shell,只要不关闭它,ld-2.7.so就一直在它的空间里被映射着,这是因为linux是基于引用计数删除被移动的文件的。只要有shell就可以,执行:
#./aaa /bin/cp ./aaa /lib/ld-2.7.so
一切恢复正常。
那么ld-2.7.so到底是什么呢?如果它作为链接器的话,它的链接器在哪里呢?实际上它是一个静态链接的so,并且它是可执行的,从它的man手册上可以看出,它就是用来加载程序以及程序需要的动态库的,然后执行程序,它本身是不依赖其它的so的,它只要OS就能运行,因此任何程序都可以看起来这样运行:
#/lib/ld-2.7.so XX [可执行文件全路径XX的参数]
比如:
#/lib/ld-2.7.so /bin/ls的效果和ls是一样的。
只不过为了方便,linux内置了对elf可执行文件的直接支持,当执行exec的时候,OS自动地直接调用了ld-2.7.so(ld-2.7.so被动态链接进了elf可执行文件,作为其一个so,可以通过ldd看出来),而实际上,更加一般的方法就是通过命令行ld-2.7.so XXX来执行elf可执行程序的(这样ld-2.7.so就不需要链接进elf可执行文件了)。既然c/c++写出的代码可以直接编译成elf可执行文件来直接执行,其它任何的语言写出的代码都应该可以直接执行,在linux中这是通过binfmt_misc来支持的,具体的可以参考内核源码Documents目录中的binfmt_misc.txt文档。现在看一下java的执行:
#java XX(XX为类文件去掉.class)
这里的java就相当于一个链接器,和/lib/ld-2.7.so是类似的,只是它做了更多,包括构建一个虚拟执行环境(建立java虚拟机)等,它启动了java类XX。
现在elf可执行文件的执行方式以及java类的执行方式更加统一了,都是连接器来调用的:ld-2.7.so XX和java XX,它们的本质其实是一样的。那么它们的互操作就不成问题了,由于java程序本身就是一个elf可执行文件,并且它是c写成的,因此java.c就表达了如何在c语言中调用java方法,只不过java.c调用了固定的java方法,那就是main,这和ld-2.7.so最终调用c语言的main函数是一样的,完全是为了规定,没有机制上的原因。既然java类本身是c语言写的程序启动的,因此对于本地代码,它肯定能回调,这就是jni接口,可以在java类中调用本地c语言写成的函数。在理解了机制以后,我们完全可以参考java.c文件写一个不调用main方法的新的java链接器:
代码参考自:www.rgagnon.com首先定义一个类,没有main函数:
class Test {
public native void func(); //定义本地方法
static {
System.loadLibrary("func1");
}
public static void sub(String[] args) {
new Test().func();
}
}
编写一个c文件-startjava.c:
#include <jni.h>
#include <stdio.h>
int main() {
JavaVM *vm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString = "-Djava.class.path=.";
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 1;
vm_args.ignoreUnrecognized = 1;
jstring jstr;
jobjectArray args;
jint res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);
jclass cls = (*env)->FindClass(env, "Test"); //找到Test类,这个也可以通过命令行传递
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "sub", "([Ljava/lang/String;)V"); //调用sub方法,而不是main
jstring argString = (*env)->NewStringUTF(env, ""); //empty arg list
args = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env, "java/lang/String"), jstr);
(*env)->CallStaticVoidMethod(env, cls, mid, args);
return 0;
}
然后定一个本地方法的实现-func1.c:
#include <jni.h>
JNIEXPORT void JNICALL Java_TestStunnel_func(JNIEnv *env, jobject obj)
{
....//随意
}
最终startjava这个elf可执行文件启动了一个没有main的java类,然后java类中又调用一个本地方法,startjava.c同样也可以没有main函数--将main改成abc,而是自己写一个链接器来执行,这个链接器负责从OS内核接手用户空间的执行(载入所需动态库-libc/libjvm等的过程早在内核分析elf的时候就做过了,因此不需要这个链接器来做),然后调用其abc函数即可。这样从最初从OS接手过来,每一个不管是elf可执行的本地文件还是java类,没有用到ld-2.7.so和java可执行程序,也没有一个拥有main函数(或者方法),然而“....//随意”真的就执行了。
java是这样,其它的比如perl,python也是这样,包括c#等,都能如此折腾!

分享到:
评论

相关推荐

    JAVA 开发环境 Java SE Development Kit (JDK) 16.0.1 x64.zip

    根据 TIOBE 编程社区索引,Java 编程语言是软件开发人员的最佳选择之一,他们力争在基于 C 和 C-based 语言中占据上风。从 PC,移动终端或媒体播放器到更复杂的医疗设备,全球大多数电子设备都使用其功能。 Java SE...

    java高效学习之路-过来人经验

    Documentation是最最重要的编程手册,涵盖了整个Java所有方面的内容的描述。可以这样说,学习Java编程,大部分时间都是花在看这个Documentation上面的。我是随身携带的,写Java代码的时候,随时查看,须臾不离手。 ...

    JAVA 开发环境 Java SE Development Kit (JDK) 16.0.1 x64 免费下载.zip

    根据 TIOBE 编程社区索引,Java 编程语言是软件开发人员的最佳选择之一,他们力争在基于 C 和 C-based 语言中占据上风。从 PC,移动终端或媒体播放器到更复杂的医疗设备,全球大多数电子设备都使用其功能。 Java SE...

    新版Android开发教程.rar

    Android 是一个专门针对移动设备的软件集,它包括一个操作系统,中间件和一些重要的应用程序。 Beta 版 的 Android SDK 提供了在 Android 平台上使用 JaVa 语言进行 Android 应用开发必须的工具和 API 接口。 特性 ...

    java 面试题 总结

    JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变...

    超级有影响力霸气的Java面试题大全文档

     JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要...

    深入理解Android:卷I--详细书签版

    这些类都是Android中最常用和最基本的,只有掌握这些类的知识,才 能在分析后续的代码时游刃有余。  第6章以MediaServer为切入点,对Binder进行了较为全面的分析。本章拓展思考部分讨论了与Binder有关的三个问题...

    Oracle SQL高级编程(资深Oracle专家力作,OakTable团队推荐)--随书源代码

     Oracle 数据库中的SQL是当今市场上功能最强大的SQL实现之一,而本书全面展示了这一工具的威力。如何才能让更多人有效地学习和掌握SQL呢?Karen Morton及其团队在本书中提供了专业的方案:先掌握语言特性,再学习...

    网管教程 从入门到精通软件篇.txt

     bootcfg 命令启动配置和故障恢复(对于大多数计算机,即 boot.ini 文件)。  含有下列参数的 bootcfg 命令仅在使用故障恢复控制台时才可用。可在命令提示符下使用带有不同参数的 bootcfg 命令。  用法:  ...

    汪文君高并发编程实战视频资源全集

    │ 高并发编程第一阶段27讲、wait和sleep的本质区别是什么,深入分析(面试常见问题).mp4 │ 高并发编程第一阶段28讲、线程生产者消费者的综合实战结合Java8语法.mp4 │ 高并发编程第一阶段29讲、如何实现一个...

    汪文君高并发编程实战视频资源下载.txt

    │ 高并发编程第一阶段27讲、wait和sleep的本质区别是什么,深入分析(面试常见问题).mp4 │ 高并发编程第一阶段28讲、线程生产者消费者的综合实战结合Java8语法.mp4 │ 高并发编程第一阶段29讲、如何实现一个...

    oracle学习文档 笔记 全面 深刻 详细 通俗易懂 doc word格式 清晰 连接字符串

    六、 oracle安装、卸载和启动  硬件要求 物理内存:1GB 可用物理内存:50M 交换空间大小:3.25GB 硬盘空间:10GB  安装 1. 安装程序成功下载,将会得到如下2个文件: 解压文件将得到database文件夹,文件组织...

    计算机程序的正确定义

     注意事项:这是一门不可或缺的软件开发课程,曾经有一本经典计算机专业书籍叫做《数据结构+算法=程序》,这说明了数据结构和算法的重要性。它能帮我们建立良好的程序分析与设计能力。  第四阶段:实现一个模拟的...

    操作系统(内存管理)

    文将对 Linux™ 程序员可以使用的内存管理技术进行概述,虽然关注的重点是 C 语言,但同样也适用于其他语言。文中将为您提供如何管理内存的细节,然后将进一步展示如何手工管理内存,如何使用引用计数或者内存池来半...

    华为编程开发规范与案例

    接口类问题(B类)-指设计、编码中出现的函数和环境、其他函数、全局/局部变量或数据变量之间的数据/控制传输不匹配的问题,在系统中起重要作用,将导致模块间配合失效等严重问题; 维护类问题(C类)-指设计、...

Global site tag (gtag.js) - Google Analytics