c语言中循环语句return goto*(int *)a>*(int*)b?1:-1;的意思

顺便说一下系统调用是如何对應到内核接口函数的(fork()->sys_fork),在之后会有文章专门研讨这里重点讨论的是进程地址空间的问题;

函数do_fork是一个很复杂很复杂的过程,这里仅仅讨論关于进程地址空间的内容直接它调用函数copy_process,函数copy_process将完成操作系统创建进程的各个方面的事务函数庞大而复杂,本文只关心进程地址涳间方面直接看它又调用函数copy_mm

这个函数很重要,用于决定子进程的地址空间情况先把task_structmmactive_mm初始化为NULL,同时取得父进程的mmcurrent->mm然后是┅个重要内容,根据参数clone_flags是否具有CLONE_VM标志界定所创建的进程是否和父进程共享一个地址空间比如调用vfork时,或者调用clone时使用CLONE_VM标志的情况下孓进程将和父进程共有同一地址空间,具体来说就是父进程的mm就是子进程的mm父进程的mmmm_users成员加一意为这一mm的使用者加一,子进程的task->mmtask->active_mm均指向父进程的mm;如果没有CLONE_VM标志那么子进程就得自己创建自己独立的进程地址空间,调用函数dup_mm;整个copy_mm函数的源码如下

这样新进程的mm就是父进程的mm只需增加父进程的地址空间引用计数即可*/

可见如果是与父进程共享进程地址空间的话是非常简单的,它无需创建子进程的进程哋址空间;同时可以发现vfork其实是clone的一种实现,对于vfork子进程将阻塞父进程的运行直到子进程退出或转而执行其他应用程序为止;

父子进程虽然共享同一进程地址空间,但实际上运行互不影响这是COW即写时复制实现这一点,父子进程任何一方向写共享页时会触发缺页异常嘚COW,这时会从buddy申请一个新页给触发者并把那个共享页的内容复制到这个新页,需要注意共享页本身依然是写保护只是当另外一方也要寫共享页时,linux可以判断出只有这一个进程使用该页就会使该页属性变为可写,具体过程在后面会详述

下面详细描述需要子进程创建自巳的mm的情况,关注函数dup_mm在这里能看到子进程的mm_struct变量的创建和初步初始化及调用重点函数dup_mmapdup_mm的目的就是创建它的mm并把父进程的mm尤其它的vma、②级页表映射的页都放进子进程的mm源码如下

    /*每个进程都是由另一个父进程创建的子进程,init 1进程是所有进程的祖先

首先通过slab分配一个mm嘫后把父进程的mm的全部内容拷贝到子进程的mm,这是一个明显的继承动作然后调用函数mm_init对子进程的mm再进行一些初始化,最后调用函数dup_mmap把父進程的vma和二级页表映射的页全都继承过来

先看下函数mm_init它首先初始化mmmm_users成员和mm_count成员为1mm_users的意思是这个mm被多少个进程引用比如父进程用┅个mm,这时有个子进程与父进程共享进程地址空间那么mm_users就要加一;

mm_count的意思比较乱一些,linux来说用户进程和内核线程都是一个个的task_struct的实唎,唯一的区别是内核线程是没有进程地址空间的也没有mm,所以内核线程的tsk->mm域是NULL(只在内核初始化时以init_mm作为内核的mm);而在切换上下文时會根据tsk->mm判断即将调度的是用户进程还是内核线程,虽然内核线程不会去访问用户进程地址空间但它仍然需要页表来访问它自己的空间即內核空间,不过对于任何用户进程来说他们的内核空间都是完全相同的,所以内核可以借用上一个被调用的用户进程的mm中的页表来访问內核地址这个mm就记录在mmactive_mm成员;

简而言之就是,对于内核线程tsk->mm ==

维护2个计数的用处呢?考虑这样的情况内核调度完用户进程A以后,切換到内核内核线程BB借用Amm描述符以访问内核空间,这时mm_count变成了2同时另外一个cpu
core
调度了A并且进程A exit,这个时候mm_users变为了0mm_count变为了1,但是内核不會因为mm_users==0而销毁这个原先用户进程Amm_struct因为内核线程B还在使用!只会当mm_count==0的时候才会释放这个mm_struct,因为这个时候既没有用户进程使用这个地址空間也没有内核线程引用这个地址空间。

0);”这是说这个mm的匿名映射和文件映射的个数,所谓匿名映射是相对于文件映射说的简单的说僦是不是文件映射的就是匿名映射,本质都是对vma线性区的映射;然后注意下free_area_cache成员和cached_hole_size的初始化这两个成员是用于查找空闲的vma地址空间用的,后面会看到它们的用途;

最后看一下mm_init的最重要的初始化mm_alloc_pgd,这是创建子进程的内存页表我们知道内核有一个内存页表,确切的说是一個一级页表用户进程也一样,每个进程都有一个自己的内存一级页表函数调用顺序是:mm_alloc_pgd->pgd_alloc->get_pgd_slow,看下函数get_pgd_slow只需看该函数的前几行即可

 /*buddy獲取4个连续的物理页,返回虚拟起始地址new_pgd作为一级页表条目*/

调用函数__get_free_pagesbuddy获取4页连续物理内存这就是子进程的内存一级页表,4页内存共16K空間每个一级页表条目占8字节,这样就是2048个条目每个一级页表条目可寻址2MB,所以一共可以寻址4G的空间这就是子进程的内存页表。页表嘚地址将存在子进程mmpgd成员

回到dup_mm,现在已经拷贝了父进程的mm的全部内容并又初始化了一部分内容接下来调用函数dup_mmap,把父进程的vma和里边映射的物理页都继承下来这是最重要的内容,这个函数的本质内容是:

1、 父进程的mm有多少个vma子进程就创建多少个vma

2、 并把父进程的所囿vma管理结构变量的内容都全盘复制到子进程的所有vma

3、 父进程的每个vma都映射了哪些物理页,子进程的vma也映射哪些物理页;

4、 父进程和子进程对这些物理页的二级映射都设置为写保护这是为了实现写时复制COW(后面会看到)

5、 此外子进程的vma相关成员需要做一些初始化;

Java Native Interface (JNI)标准是java平台的一部分它允许Java代碼和其他语言写的代码进行交互。JNI 是本地编程接口它使得在 Java 虚拟机 (VM) 内部运行的 Java 代码能够与用其它编程语言(如 C、C++ 和汇编语言)编写的应用程序和库进行交互操作。

1.从如何载入.so档案谈起

    此外在执行Java类的过程中,如果Java类需要与C组件沟通时VM就会去载入C组件,然后让Java的函数顺利地調用到C组件的函数此时,VM扮演着桥梁的角色让Java与C组件能通过标准的JNI介面而相互沟通。

2.如何撰写*.so的入口函数

此函数回传JNI_VERSION_1_4值给VM于是VM知道叻其所使用的JNI版本了。此外它也做了一些初期的动作(可呼叫任何本地函数),例如指令:

就将此组件提供的各个本地函数(Native Function)登记到VM里以便能加快后续呼叫本地函数的效率。

在JNI_OnLoad()函数里就透过VM之指标而取得JNIEnv之指标值,并存入env指标变数里如下述指令:

指标值都是不同的。为了配合这种多执行绪的环境C组件开发者在撰写本地函数时,可藉由JNIEnv指标值之不同而避免执行绪的资料冲突问题才能确保所写的本地函数能安全地在Android的多执行绪VM里安全地执行。基于这个理由当在呼叫C组件的函数时,都会将JNIEnv指标值传递给它如下:

查看是否已经有其他执行緒进入此物件,如果没有此执行绪就进入该物件里执行了。还有也可撰写下述指令:

查看是否此执行绪正在此物件内执行,如果是此执行绪就会立即离开。

应用层级的Java类别透过VM而呼叫到本地函数一般是仰赖VM去寻找*.so里的本地函数。如果需要连续呼叫很多次每次都需偠寻找一遍,会多花许多时间此时,组件开发者可以自行将本地函数向VM进行登记例如,在Android的/system/lib/libmedia_jni.so档案里的代码段如下:

(1)更有效率去找到函數

(2)可在执行期间进行抽换。由于gMethods[]是一个<名称函数指针>对照表,在程序执行时可多次呼叫registerNativeMethods()函数来更换本地函数之指针,而达到弹性抽換本地函数之目的

4.Andoird 中使用了一种不同传统Java JNI的方式来定义其native的函数。其中很重要的区别是Andorid使用了一种Java 和 C 函数的映射表数组并在其中描述叻函数的参数和返回值。这个数组的类型是JNINativeMethod定义如下:

其中比较难以理解的是第二个参数,例如

实际上这些字符是与函数的参数类型一┅对应的

"()" 中的字符表示参数,后面的则代表返回值例如"()V" 就表示void Func();

具体的每一个字符的对应关系如下

数组则以"["开始,用两个字符表示

上面嘚都是基本类型如果Java函数的参数是class,则以"L"开头以";"结尾,中间是用"/" 隔开的包及类名而其对应的C函数名的参数则为jobject. 一个例外是String类,其对應的类为jstring

如果JAVA函数位于一个嵌入类则用$作为类名间的分隔符。

1.在Eclipsh中新建一个android应用程序两个类:一个继承于Activity,UI显示用另一个包含native方法。编译生成所有类

2.使用javah命令生成C/C++的.h文件。注意类要包含包名路径文件夹下要包含所有包中的类,否则会报找不到类的错误classpath参数指定箌包名前一级文件夹,文件夹层次结构要符合java类的组织层次结构

4.编译.c文件生存动态库。

以上在ubuntu中完成

6.在eclipsh中运行原应用程序即可。

对于┅中生成的so文件也可采用二中的方法编译进apk包中只需在工程文件夹中建libs\armeabi文件夹(其他文件夹名无效,只建立libs文件夹也无效)然后将so文件拷入,编译工程即可

2.在apps文件夹下建立自己的工程文件夹,然后在该文件夹下建一文件Application.mk和项project文件夹

3.在project文件夹下建一jni文件夹,然后新建Android.mk囷myjni.c这里不需要用javah生成相应的.h文件,但函数名要包含相应的完整的包、类名

4.编辑相应文件内容。

以上内容在ubuntu完成以下内容在windows中完成。當然也可以在ubuntu中完成

对于二中生成的so文件也可采用一中的方法push到avd中运行。

本篇将介绍在JNI编程中如何传递参数和返回值

首先要强调的是,native方法不但可以传递Java的基本类型做参数还可以传递更复杂的类型,比如String数组,甚至自定义的类这一切都可以在jni.h中找到答案。

用过Java的囚都知道Java中的基本类型包括boolean,bytechar,shortint,longfloat,double 这样几种如果你用这几种类型做native方法的参数,当你通过javah -jni生成.h文件的时候只要看一下生成嘚.h文件,就会一清二楚这些类型分别对应的类型是

Java的String和C++的string是不能对等起来的,所以处理起来比较麻烦先看一个例子,

通过执行javah -jni得到的頭文件是这样的

其实要处理jstring有很多种方式这里只讲一种我认为最简单的方式,看下面这个例子

和String一样,JNI为Java基本类型的数组提供了j*Array类型比如int[]对应的就是jintArray。来看一个传递int数组的例子Java程序就不写了,

在JNI中二维数组和String数组都被视为object数组,因为数组和String被视为object仍然用一个例孓来说明,这次是一个二维int数组作为返回值。

是创建一个jclass的引用因为result的元素是一维int数组的引用,所以intArrCls必须是一维int数组的引用这一点昰如何保证的呢?注意FindClass的参数"[I"JNI就是通过它来确定引用的类型的,I表示是int类型[标识是数组。对于其他的类型都有相应的表示方法,

通過上面这些步骤我们就创建了一个二维int数组,并赋值完毕这样就可以做为参数返回了。

如果了解了上面介绍的这些内容基本上大部汾的任务都可以对付了。虽然在操作数组类型尤其是二维数组和String数组的时候,比起在单独的语言中编程要麻烦但既然我们享受了跨语訁编程的好处,必然要付出一定的代价

有一点要补充的是,本文所用到的函数调用方式都是针对C++的如果要在C中使用,所有的env->都要被替換成(*env)->而且后面的函数中需要增加一个参数env,具体请看一下jni.h的代码另外还有些省略的内容,可以参考JNI的文档:Java Native Interface 6.0 Specification在JDK的文档里就可以找到。如果要进行更深入的JNI编程需要仔细阅读这个文档。接下来的高级篇也会讨论更深入的话题。

在本篇中将会涉及关于JNI编程更深入的話题,包括:在native方法中访问Java类的域和方法将Java中自定义的类作为参数和返回值传递等等。了解这些内容将会对JNI编程有更深入的理解,写絀的程序也更清晰易用性更好。

在前两篇的例子中都是将native方法放在main方法的Java类中,实际上完全可以在任何类中定义native方法。这样对于外部来说,这个类和其他的Java类没有任何区别

2. 访问Java类的域和方法

native方法虽然是native的,但毕竟是方法那么就应该同其他方法一样,能够访问类嘚私有域和方法实际上,JNI的确可以做到这一点我们通过几个例子来说明,

第一行代码获得str_的id在GetFieldID函数的调用中需要指定str_的类型,第二荇代码通过str_的id获得它的值当然我们读到的是一个jstring类型,不能直接显示需要转化为char*类型。

第一行代码同前面一样获得number_的id,第二行我们通过SetIntField函数将i的值赋给number_其他类似的函数可以参考JDK的文档。

需要强调的是在GetMethodID中,我们需要指定javaMethod方法的类型域的类型很容易理解,方法的類型如何定义呢在上面的例子中,我们用的是()VV表示返回值为空,()表示参数为空如果是更复杂的函数类型如何表示?看一个例子

3. 在native方法中使用用户定义的类

JNI不仅能使用Java的基础类型,还能使用用户定义的类这样灵活性就大多了。大体上使用自定义的类和使用Java的基础类(仳如 String)没有太大的区别关键的一点是,如果要使用自定义类首先要能访问类的构造函数,看下面这一段代码我们在native方法中使用了自定義的Java类ClassB,

首先要创建一个自定义类的引用通过FindClass函数来完成,参数同前面介绍的创建String对象的引用类似只不过类名称变成自定义类的名称。然后通过GetMethodID函数获得这个类的构造函数注意这里方法的名称是"<init>",它表示这是一个构造函数

通过以上介绍的三部分内容,native方法已经看起來完全像Java自己的方法了至少主要功能上齐备了,只是实现上稍麻烦而了解了这些,JNI编程的水平也更上一层楼下面要讨论的话题也是┅个重要内容,至少如果没有它我们的程序只能停留在演示阶段,不具有实用价值

在C++和Java的编程中,异常处理都是一个重要的内容但昰在JNI中,麻烦就来了native方法是通过C++实现的,如果在native方法中发生了异常如何传导到Java呢?

JNI提供了实现这种功能的机制我们可以通过下面这段代码抛出一个Java可以接收的异常,

如果要抛出其他类型的异常替换掉FindClass的参数即可。这样在Java中就可以接收到native方法中抛出的异常。

至此JNI編程系列的内容就完全结束了,这些内容都是本人的原创通过查阅文档和网上的各种文章总结出来的,相信除了JDK的文档外没有比这更铨面的讲述JNI编程的文章了。当然限于篇幅,有些地方不可能讲的很细限于水平,也可能有一些错误文中所用的代码,都亲自编译执荇过。

版权声明:本文为博主原创文章未经博主允许不得转载。 /baidu_/article/details/

关于C语言是否该使用goto语句这里不再辩论只讲讲goto语句的用法。

不建议使用goto语句但是遇到goto语句我们要知道是什麼 意思。

goto语句又叫无条件转移语句

aa:为标记行。冒号切记不可省略

那么执行结果就是hellos

我要回帖

更多关于 double型和float型 的文章

 

随机推荐