【考勤系统源码app】【tcp/ip源码分析】【b/s源码之家】fstat源码

时间:2024-12-28 09:51:57 来源:api 查询源码 分类:焦点

1.linux中cp命令如何用 C语言实现
2.记一次源码追踪分析,从Java到JNI,再到JVM的C++:fileChannel.map()为什么快;源码分析map方法,put方法

fstat源码

linux中cp命令如何用 C语言实现

       1,首先需要了解cp的原理。

       2,可以参考cp的源码去了解其原理

       3,cp命令的源码可以在linux内核中找到。

       4,或者下载busybox其中也会有cp的源码

       åªæœ‰äº†è§£å…¶åŽŸç†ä¹‹åŽæ‰èƒ½è°ˆå¦‚何实现。参考代码如下:

#include <stdio.h>

       #include <stdlib.h>

       #include <sys/stat.h>

       #include <sys/types.h>

       #include <fcntl.h>

       #include <errno.h>

       #include <unistd.h>

       #include <string.h>

       #define BUF_SIZE 

       #define PATH_LEN 

       void my_err(char *err_string, int line )

       {

           fprintf(stderr,"line:%d ",line);

           perror(err_string); 

           exit(1);

       }

       void copy_data(const int frd,const int fwd)

       {

           int read_len = 0, write_len = 0;

           unsigned char buf[BUF_SIZE], *p_buf;

           while ( (read_len = read(frd,buf,BUF_SIZE)) ) {

               

               if (-1 == read_len) {

                   my_err("Read error", __LINE__);

               }

               else if (read_len > 0) {  //把读取部分写入目标文件

                   p_buf = buf;

                   while ( (write_len = write(fwd,p_buf,read_len)) ) {

                       if(write_len == read_len) {

                           break;

                       }

                       else if (write_len > 0) {  //只写入部分

                           p_buf += write_len;

                           read_len -= write_len;

                       }

                       else if(-1 == write_len) {

                           my_err("Write error", __LINE__);

                       }

                   }

                   if (-1 == write_len) break;

               }

           }

       }

       int main(int argc, char **argv) 

       {

           

           int frd, fwd; //读写文件描述符

           int len = 0;

           char *pSrc, *pDes; //分别指向源文件路径和目标文件路径

           struct stat src_st,des_st;

           

           if (argc < 3) {

               printf("用法 ./MyCp <源文件路径> <目标文件路径>\n");

               my_err("arguments error ", __LINE__);

           }

           

           frd = open(argv[1],O_RDONLY);

           if (frd == -1) {

               my_err("Can not opne file", __LINE__);

           }

           if (fstat(frd,&src_st) == -1) {

               my_err("stat error",__LINE__);

           }

           /*检查源文件路径是否是目录*/

           if (S_ISDIR(src_st.st_mode)) {

               my_err("略过目录",__LINE__);

           }

           

           pDes = argv[2];

           stat(argv[2],&des_st);

           if (S_ISDIR(des_st.st_mode)) {  //目标路径是目录,则使用源文件的文件名

               

               len = strlen(argv[1]);

               pSrc = argv[1] + (len-1); //指向最后一个字符

               /*先找出源文件的文件名*/

               while (pSrc >= argv[1] && *pSrc != '/') {

                   pSrc--;

               }

               pSrc++;//指向源文件名

               

               len = strlen(argv[2]); 

               // . è¡¨ç¤ºå¤åˆ¶åˆ°å½“前工作目录

               if (1 == len && '.' == *(argv[2])) {

                   len = 0; //没有申请空间,后面就不用释放

                   pDes = pSrc;

               }

               else {  //复制到某目录下,使用源文件名

                   pDes = (char *)malloc(sizeof(char)*PATH_LEN);

                   if (NULL == pDes) {

                       my_err("malloc error ", __LINE__);

                   }

                   

                   strcpy(pDes,argv[2]);

               

                   if ( *(pDes+(len-1)) != '/' ) {  //目录缺少最后的'/',则补上’/‘

                       strcat(pDes,"/");

                   }

                   strcat(pDes+len,pSrc);

               }

           }

           

           /* æ‰“开目标文件, ä½¿æƒé™ä¸Žæºæ–‡ä»¶ç›¸åŒ*/ 

           fwd = open(pDes,O_WRONLY | O_CREAT | O_TRUNC,src_st.st_mode);

           if (fwd == -1) {

               my_err("Can not creat file", __LINE__);

           }

           copy_data(frd,fwd);

           //puts("end of copy");

           if (len > 0 && pDes != NULL)

               free(pDes);

           

           close(frd);

           close(fwd);

           return 0;

       }

记一次源码追踪分析,从Java到JNI,再到JVM的考勤系统源码appC++:fileChannel.map()为什么快;源码分析map方法,put方法

       前言

       在系统IO相关的系统调用有read/write,mmap,sendfile等这些。

       其中read/write是普通的读写,每次都需要将buffer从用户空间拷贝到内核空间;

       而mmap使用的是内存映射,会将磁盘文件对应的页映射(拷贝)到内核空间的page cache,并记录到用户进程的页表中,使得用户空间也可以像操作用户空间一样操作该文件的映射,最后再由操作系统来讲该映射(脏页)回写到磁盘;

       sendfile则使用的是零拷贝技术,在mmap的基础上,当发送数据的tcp/ip源码分析时候只拷贝fd和offset等元数据信息,而将数据主体直接拷贝至protocol buffer,实现了内核数据零冗余的零拷贝技术

       本文地址:/post//

问题/目的问题1Java中哪些API使用到了mmap问题2怎么知道该API使用到了mmap,如何追踪程序的系统调用目的1源码中分析验证,从Java到JNI,再到C++:fileChannel.map()使用的是系统调用mmap目的2源码验证分析:调用mmapedByteBuffer.put(Byte[])时JVM在搞些什么?mmap比普通的read/write快在哪?揭晓答案1mmap在Java NIO中的体现/使用

       看一个例子

// 1GBpublic static final int _GB = 1**;File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer mmapedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB);for (int i = 0; i < _GB; i++) { count++;mmapedByteBuffer.put((byte)0);}

       其中fileChannel.map()底层使用的就是系统调用mmap,函数签名为: public abstract MappedByteBuffer map(MapMode mode,long position, long size)throws IOException

答案2程序执行的系统调用追踪/** * @author Tptogiar * @description * @date /5/ - : */public class TestMappedByteBuffer{ public static final int _4kb = 4*;public static final int _GB= 1**;public static void main(String[] args) throws IOException, InterruptedException { // 为了方便在日志中找到本段代码的开始位置和结束位置,这里利用文件io来打开始标记FileInputStream startInput = null;try { startInput = new FileInputStream("start1.txt");startInput.read();} catch (IOException e) { e.printStackTrace();}File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB); //我们想分析的语句问题2for (int i = 0; i < _GB; i++) { map.put((byte)0); // 下文中需要分析的语句目的2}// 打结束标记FileInputStream endInput = null;try { endInput = new FileInputStream("end.txt");endInput.read();} catch (IOException e) { e.printStackTrace();}}}

       把上面这段代码编译后把“.class”文件拉到linux执行,并用linux上的b/s源码之家strace工具记录其系统调用日志,拿到日志文件我们可以在日志中看到以下信息(关于怎么拿到日志可以参照我的博文:无(代写)):

       注:日志有多行,这里只选取我们关注的

// ...// 看到了我们打的开始标志openat(AT_FDCWD, "start1.txt", O_RDONLY) = -1 ENOENT (No such file or directory)// ... // 打开文件,文件描述符fd为6openat(AT_FDCWD, "filename", O_RDWR|O_CREAT, ) = 6// 判断文件状态fstat(6, { st_mode=S_IFREG|, st_size=, ...}) = 0// ... // 判断文件状态fstat(6, { st_mode=S_IFREG|, st_size=, ...}) = 0// 进行内存映射mmap(NULL, , PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x7f2fd6cd// ...// 程序退出exit(0)// 看到了我们打的结束标志openat(AT_FDCWD, "end.txt", O_RDONLY) = -1 ENOENT (No such file or directory)

       在上面程序的系统调用日志中我们确实看到了我们打的开始标志,结束标志。在开始标志和结束标志之间我们看到了我们的文件"filename"确实被打开了,文件描述符fd = 6;在打开文件后紧接着又执行了系统调用mmap,这一点我们Java代码一致,这样,新年电子贺卡+源码我们就验证了我们答案1中的结论,可以开始我们的下文了

源码追踪分析,从Java到JNI,再到JVM的C++目的1寻源之旅:fileChannel.map()

       我们知道我们执行Java代码fileChannel.map()确实会在底层调用系统调用,那怎么在源码中得到验证呢?怎么落脚于源码进行分析呢?下面开始我们的寻源之旅

       FileChannelImpl.map() 注:由于代码较长,这里代码中略去了一些我们不关注的,比如异常捕获等

public MappedByteBuffer map(MapMode mode, long position, long size)throws IOException{ // ...try { // ...synchronized (positionLock) { // ...long mapPosition = position - pagePosition;mapSize = size + pagePosition;try { // !我们要找的语句就在这!addr = map0(imode,谷得游戏+源码 mapPosition, mapSize);} catch (OutOfMemoryError x) { // 如果内存不足,先尝试进行GCSystem.gc();try { Thread.sleep();} catch (InterruptedException y) { Thread.currentThread().interrupt();}try { // 再次试着mmapaddr = map0(imode, mapPosition, mapSize);} catch (OutOfMemoryError y) { // After a second OOME, failthrow new IOException("Map failed", y);}}} // ...} finally { // ...}}

       上面函数源码中真正执行mmap的语句是在addr = map0(imode, mapPosition, mapSize),于是我们寻着这里继续追踪

       FileChannelImpl.map0()

// Creates a new mappingprivate native long map0(int prot, long position, long length)throws IOException;

       可以看到,该方法是一个native方法,所以后面的源码我们需要到这个FileChannelImpl.class对应的fileChannelImpl.c中去看,所以我们需要去找到JDK的源码

       在JDK源码中我们找到fileChannelImpl.c文件

       fileChannelImpl.c 根据JNI的对应规则,我们找到该文件内对应的Java_sun_nio_ch_FileChannelImpl_map0方法,其源码如下:

JNIEXPORT jlong JNICALLJava_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this, jint prot, jlong off, jlong len){ void *mapAddress = 0;jobject fdo = (*env)->GetObjectField(env, this, chan_fd);jint fd = fdval(env, fdo);int protections = 0;int flags = 0;if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) { protections = PROT_READ;flags = MAP_SHARED;} else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) { protections = PROT_WRITE | PROT_READ;flags = MAP_SHARED;} else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) { protections =PROT_WRITE | PROT_READ;flags = MAP_PRIVATE;}// !我们要找的语句就在这里!mapAddress = mmap(0,/* Let OS decide location */len,/* Number of bytes to map */protections,/* File permissions */flags,/* Changes are shared */fd, /* File descriptor of mapped file */off); /* Offset into file */if (mapAddress == MAP_FAILED) { if (errno == ENOMEM) { JNU_ThrowOutOfMemoryError(env, "Map failed");return IOS_THROWN;}return handle(env, -1, "Map failed");}return ((jlong) (unsigned long) mapAddress);}

       我们要找的语句就上面代码中的mapAddress = mmap(0,len,protections,flags,fd,off),至于为什么不是直接的mmap,而是mmap,是因为这里的mmap是一个宏,在文件上方有其定义,如下:

#define mmap mmap

       至此,我们就在源码中得到验证了我们问题2中的结论:fileChannelImpl.map()底层使用的是mmap系统调用

目的2寻源之旅:mmapedByteBuffer.put(Byte[ ])

       接着我们来看看当我们调用mmapedByteBuffer.put(Byte[])JVM底层在搞些什么动作

       MappedByteBuffer ?首先我们得知道,当我们执行MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB)时,实际返回的对象是DirectByteBuffer类的实例,因为MappedByteBuffer为抽象类,且只有DirectByteBuffer继承了它,看下面两图就明白了

       DirectByteBuffer 于是我们找到DirectByteBuffer内的put(Byte[ ])方法

public ByteBuffer put(byte x) { unsafe.putByte(ix(nextPutIndex()), ((x)));return this;}

       可以看到该方法内实际是调用Unsafe类内的putByte方法来实现功能的,所以我们还得去看Unsafe类

       Unsafe.class

public native voidputByte(long address, byte x);

       该方法在Unsafe内是一个native方法,所以所以我们还得去看unsafe.cpp文件内对应的实现

       unsafe.cpp

       在JDK源码中,我们找到unsafe.cpp

       在这份源码内,没有使用JNI内普通加前缀的方法来形成对应关系

       不过我们还是能顺着源码的蛛丝轨迹找到我们要找的方法

       注意到源码中有这样的注册机制,所以我们可以知道我们要找的代码就是上图中标注的代码

       顺藤摸瓜,我们就找到了该方法的定义

UNSAFE_ENTRY(void, Unsafe_SetNative##Type(JNIEnv *env, jobject unsafe, jlong addr, java_type x)) \UnsafeWrapper("Unsafe_SetNative"#Type); \JavaThread* t = JavaThread::current(); \t->set_doing_unsafe_access(true); \void* p = addr_from_java(addr); \*(volatile native_type*)p = x; \t->set_doing_unsafe_access(false); \UNSAFE_END \

       该方法内主要的逻辑语句就是以下两句:

/** * @author Tptogiar * @description * @date /5/ - : */public class TestMappedByteBuffer{ public static final int _4kb = 4*;public static final int _GB= 1**;public static void main(String[] args) throws IOException, InterruptedException { // 为了方便在日志中找到本段代码的开始位置和结束位置,这里利用文件io来打开始标记FileInputStream startInput = null;try { startInput = new FileInputStream("start1.txt");startInput.read();} catch (IOException e) { e.printStackTrace();}File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB); //我们想分析的语句问题2for (int i = 0; i < _GB; i++) { map.put((byte)0); // 下文中需要分析的语句目的2}// 打结束标记FileInputStream endInput = null;try { endInput = new FileInputStream("end.txt");endInput.read();} catch (IOException e) { e.printStackTrace();}}}0

       至此,我们就知道:其实我们调用mmapedByteBuffer.put(Byte[ ])时,JVM底层并不需要涉及到系统调用(这里也可以用strace工具追踪从而得到验证)。也就是说通过mmap映射的空间在内核空间和用户空间是共享的,我们在用户空间只需要像平时使用用户空间那样就行了————获取地址,设置值,而不涉及用户态,内核态的切换

总结

       fileChannelImpl.map()底层用调用系统函数mmap

       fileChannelImpl.map()返回的其实不是MappedByteBuffer类对象,而是DirectByteBuffer类对象

       在linux上可以通过strace来追踪系统调用

       JNI中“.class”文件内方法与“.cpp”文件内函数的对应关系不止是前缀对应的方法,还可以是注册的方式,这一点的追寻代码的时候有很大帮助

       directByteBuffer.put()方法底层并没有涉及系统调用,也就不需要涉及切态的性能开销(其底层知识执行获取地址,设置值的操作),所以mmap的性能就比普通读写read/write好

       ...

原文:/post/