精华内容
下载资源
问答
  • 文献阅读梳理

    2014-03-09 20:52:00
    Finding Core Topics: Topic Extraction with Clustering on Tweet 挺水的文章,其中的借鉴可以是: 1)以转发数作为微博质量的衡量标准,区分高质量微博和...3)没有定量的评价,可以看看其如何进行定性的评价...

    Finding Core Topics: Topic Extraction with Clustering on Tweet

    挺水的文章,其中的借鉴可以是:

    1)以转发数作为微博质量的衡量标准,区分高质量微博和垃圾微博(或许可以综合考虑评论,转发,赞的数目?)

    2)通过聚类的方法进行话题提取,种子的选取是提取双引号以内的,以及首字母大写的。

    3)没有定量的评价,可以看看其如何进行定性的评价

    SEARCHINGTELEVISIONFRAGMENTSUSING MICRO-BLOGS: WHO IS ON#DWDDAND
    WHAT DO THETWEETSREPORT?

    1)n-gram结合tf-idf,结合微博自身的hashtag进行话题提取。

    On the Generation of Rich Content Metadata from Social Media

    这篇文章解决了两个任务,就这一点来看如果自己论文分为两步:filtering 和 analysis的时候或许具有借鉴意义。

     Towards an effective identification of microblog messages for social tv

     

    转载于:https://www.cnblogs.com/bobodeboke/p/3590593.html

    展开全文
  • 其次,如何进行价值共创是价值共创研究中相对集中的课题。 研究内容可进一步分为价值共创中的角色研究,价值共创中的过程研究和管理问题研究。 最后,在价值共同创造的影响结果中,更多的研究表明,价值共同创造对...
  • 编写LLVM Pass模块知识点梳理

    千次阅读 2020-02-19 16:53:29
    一、本文目标 LLVM 平台提供了 Pass 模块编写功能,类似于一种插件,...以不透明谓词为例,目前大量文献甚至包括 OLLVM 这套工具在内都是以实现一些简单算式为目标,更多复杂数据结构如何植入并未系统介绍 本...

    一、本文目标

    • LLVM 平台提供了 Pass 模块编写功能,类似于一种插件,可以对高级语言源代码被前端处理后生成的中间 IR 语句进行处理
    • 基于此特性,可以将自己设计的代码混淆的一些机制、语句编写成Pass模块来作用于目标代码上,并且具有跨平台的效果
    • 以不透明谓词为例,目前大量文献甚至包括 OLLVM 这套工具在内都是以实现一些简单算式为目标,更多复杂数据结构如何植入并未系统介绍
    • 本文旨在以C语言为原型,整理部分常见的语句结构如何转化成 Pass 对应的代码,这里只提供本人的一种实现方法供参考,如有更好的实现方案欢迎网友在评论区补充,或者后续我在博客里更新
    • 注:Pass模块中涉及到的相关API、LLVM自定义的数据类型、以及Pass模块在LLVM源代码中的注册方式,LLVM 官方提供了在线手册供查阅,本文不再赘述,相关问题欢迎在评论区探讨

    二、自定义混淆代码转化为Pass的一种工作流程

    根据自己的工作经验,推荐按如下流程来开展混淆代码转化的工作:

    1. 先用高级语言(比如C语言)将代码混淆的设计写出原型,并编程生成程序文件先验证设计能否满足预期要求;
    2. 使用clang/clang++ 对原型设计代码编译输出中间IR,此过程需使用编译选项 -emit-llvm-S,编译出中间 IR 语句主要是便于观察程序内部的转化细节;
    3. 观察自己的原型代码在IR层的映射,特别是每个Function内部BasicBlock块的生成以及BR指令形成的BasicBlock块的跳转关系。如果生成的BB块(BasicBlock)较多,建议使用 Visio 或者其他绘图工具先辅助绘制出每个 Function下的BB块跳转流程图
    4. 编写Pass模块的代码,将上面原型设计中的映射的每条 IR 指令转化成 Pass 模块中的 API 调用,步骤3中绘制的跳转流程图可以辅助厘清BB块的跳转关系,减少出错
    5. 写好 Pass 模块代码后,可以集成到 LLVM 源代码(建议将clang的代码也集成在里面)中一起编译生成一套新的clang/LLVM工具,然后使用 clang/clang++ 工具使用选项-emit-llvm-S编译待混淆的目标代码,在 IR 层观察是否植入了设计的混淆代码,并生成可执行文件运行观察混淆设计是否会影响程序执行效果。

    三、一些常见语句结构在Pass中的实现

    我们用C语言设计一个返回值为int型的函数foo作为一种不透明谓词的实现,并以此来演示上述流程,代码所在文件命名为foo.c,内容如下,基本功能是对数组元素求和。这里将负责实现植入此函数的Pass模块命名为FooPass,对应的代码文件名为FooPass.cpp

    int foo(int* a, int len)
    {
    	int sum = 0;
    	for (int i = 0; i<len; i++)
    		sum += a[i];
    	return sum;
    }
    

    3.1 生成、植入Function

    关键API整理如下

    FunctionType::get()
    Function::Create()
    

    参考前面的工作流程,首先对 foo.c 文件编译生成中间 IR 进行观察,命令如下,使用的clang版本为9.0:

    $ clang foo.c -emit-llvm -S -o foo.ll
    

    得到foo.ll后,其中关键内容节选如下,包含了foo函数的全部功能(clang 4.0 编译输出中没有dso_local关键字):

    ; Function Attrs: noinline nounwind optnone uwtable
    define dso_local i32 @foo(i32*, i32) #0 {
      %3 = alloca i32*, align 8
      %4 = alloca i32, align 4
      %5 = alloca i32, align 4
      %6 = alloca i32, align 4
      store i32* %0, i32** %3, align 8
      store i32 %1, i32* %4, align 4
      store i32 0, i32* %5, align 4
      store i32 0, i32* %6, align 4
      br label %7
    
    7:                                                ; preds = %19, %2
      %8 = load i32, i32* %6, align 4
      %9 = load i32, i32* %4, align 4
      %10 = icmp slt i32 %8, %9
      br i1 %10, label %11, label %22
    
    11:                                               ; preds = %7
      %12 = load i32*, i32** %3, align 8
      %13 = load i32, i32* %6, align 4
      %14 = sext i32 %13 to i64
      %15 = getelementptr inbounds i32, i32* %12, i64 %14
      %16 = load i32, i32* %15, align 4
      %17 = load i32, i32* %5, align 4
      %18 = add nsw i32 %17, %16
      store i32 %18, i32* %5, align 4
      br label %19
    
    19:                                               ; preds = %11
      %20 = load i32, i32* %6, align 4
      %21 = add nsw i32 %20, 1
      store i32 %21, i32* %6, align 4
      br label %7
    
    22:                                               ; preds = %7
      %23 = load i32, i32* %5, align 4
      ret i32 %23
    }
    

    注意到这里是用C语言编译得到的输出,函数名不会受到C++命名混淆机制(name mangling)的影响,保留为原始的foo

    上述示例代码中包含了foo.c的主体IR指令,我们依据Function、BasicBlock、Instruction这三个层次来剖析这个函数的结构,并对应介绍如何手动自定义植入这些内容:

    • 首先, 关键字define…{…}覆盖的区域构成一个 Function 对象的内容;
    • 其次,诸如7:11:等标签开启的一小段指令构成一个BasicBlock(常见以 brret 指令结束)
      • 需要说明的是,每个Function对象中首个入口BB块(entryBB)在IR中不会显式给出诸如entry:之类的标签开启一段代码,而是直接紧跟define从下面一行直接开始,如果涉及到遍历BB块处理的时候要注意第一个BB块所对应的位置。
    • 最后,每个BB块中每一行内容构成一个Instruction对象。

    这里的Module对应foo.ll文件,我们计划在目标Module中植入上述foo函数,首先是构造Function对象的声明语句,接着是填充这个Function对象的BB块与Instruction。这个过程会对原始程序进行改动,根据LLVM手册说明,如果要对原始程序的函数进行增删,那么应该在Pass模块的doInitialization(){}虚函数中加以实现,这里假定将自定义Function的植入操作封装在一个void CreateFooFunc(Module &M)的成员函数中。根据LLVM手册说明,由于调用该成员函数会涉及到对被植入对象的改动,doInitialization(){}的返回值应该设为true,初步构建Pass模块代码框架如下:

    namespace {
    struct FooPass : public FunctionPass {
        static char ID;
        FooPass() : ModulePass(ID) {}
    
        void CreateFooFunc(Module &M){
            // write something here
            ...
        }
    
        virtual bool doInitialization(Module &M){
            CreateFooFunc();
            return true;
        }
    
        bool runOnFunction(Function &F) {
            ...
        }
        return false;
    }
    }; 
    

    接下来就是填充void CreateFooFunc(Module &M)的函数体部分,该成员函数主要完成以下两个任务:

    1. 向当前Module中植入foo函数的声明
    2. 植入foo函数的函数体

    先介绍如何向Module中植入foo函数的声明,对应到前面介绍的工作流程步骤3,可观察到在IR层foo函数的声明如下(dso_local关键字可忽略):

    define dso_local i32 @foo(i32*, i32) 
    

    foo函数在IR层被表示为返回值为i32型,参数类型依次为i32*,i32,那么在void CreateFooFunc(Module &M)中写入语句如下:

    void CreateFooFunc(Module &M) {
    	// create the definition of foo func in the Module M
    	//===---- prepare the type var and constant nums to simplify the codes ---===
    	IntegerType* i32 = llvm::IntegerType::get(M.getContext(), 32);			// i32 type
    	PointerType* i32_ptr = llvm::IntegerType::getInt32PtrTy(M.getContext());		// i32* type
    	Constant* zero = llvm::ConstantInt::get(M.getContext(), llvm::APInt(32, 0, true));
    	//===--- create the argu list (i32*, i32)---===
    	std::vector<Type*> argsList;
    	argsList.push_back(i32_ptr);			// i32* type
        argsList.push_back(i32);
    	//===--- declare the func prototype in the Module M ---===
    	FunctionType* funcDef = FunctionType::get(i32, argsList, false);
    	//===--- create Function* object for foo func ---===
    	// create name string "foo"
    	std::ostringstream oss;
    	oss << "foo" ;
    	std::string funcNameStr = oss.str();
    	// create func ptr
    	Function* fooFuncPtr = Function::Create(funcDef, Function::ExternalLinkage, funcNameStr, &M);
    	// Now, we have finished inserting the declaration of a custom func in the Mdoule M.
    	......
    }
    

    以上语句向Module M中植入了一条函数声明语句,第一个关键APIFunctionType::get()负责生成Module中的函数声明,传入的第一个参数指定了自定义函数的返回值类型,第二个参数传入一个容器类,里面按从左到右的顺序依次存放了自定义函数的各个参数类型,这个过程没有涉及到函数命名的问题。

    第二个关键APIFunction::Create()接收上面生成的函数声明,并指定函数命名,在Module中植入了一个函数体为空的“foo”函数。
    至此,向Module中植入Function的初步工作完成,下面介绍如何填充foo函数的函数体。

    3.2 生成、植入 BasicBlock

    关键API整理如下

    llvm::BasicBlock::Create()
    

    接上,观察foo函数生成的IR语句,我们可以梳理出BasicBlock块如下:

    define dso_local i32 @foo(i32*, i32) #0 {
      [entryBB:]
      %3 = alloca i32*, align 8
      ...
      
      7:                                                ; preds = %19, %2
      %8 = load i32, i32* %6, align 4
      ...
      
      11:                                               ; preds = %7
      %12 = load i32*, i32** %3, align 8
      ...
      
      19:                                               ; preds = %11
      %20 = load i32, i32* %6, align 4
      ...
      
      22:                                               ; preds = %7
      %23 = load i32, i32* %5, align 4
      ...
    

    由此可见,foo函数一共由5个BasicBlock块组成,而且仔细观察BB块7:如下,可以发现此BB块末尾使用了一个条件分支指令br结束,其中判断条件部分则使用一条icmp比较指令生成,这些特征可以很容易联想到for循环的条件判断部分:

    7:                                                ; preds = %19, %2
      %8 = load i32, i32* %6, align 4
      %9 = load i32, i32* %4, align 4
      %10 = icmp slt i32 %8, %9
      br i1 %10, label %11, label %22
    

    再看BB块19: 如下,里面包含了一条加法指令add,对变量%20执行加1操作,可看出这个BB块有变量自增特征,并且末尾采用了一条无条件分支指令br,跳转目标指向前面的BB块7:,可以判断出这个BB块负责了for循环的循环变量自增部分:

    19:                                               ; preds = %11
      %20 = load i32, i32* %6, align 4
      %21 = add nsw i32 %20, 1
      store i32 %21, i32* %6, align 4
      br label %7
    

    那么可知在条件和自增BB块中间则包含了for循环的循环体部分,在本示例中for循环体只有一个BB块,如果for循环内部比较复杂,则中间循环体部分也可能由多个BB块组成。

    按照本文前面介绍的工作流程,这里先绘制出这个函数BB块之间的跳转关系示意图如下,绘制这个图示可帮助厘清复杂函数植入时的BB块跳转关系:
    在这里插入图片描述
    按照上面这个顺序,继续往void CreateFooFunc(Module &M)函数中添加语句如下,这个过程要用到前面创建的Function* fooFuncPtr

    void CreateFooFunc(Module &M) {
    	...
    	// Now, we have finished inserting the declaration of a custom func in the Mdoule M.
    	//===--- prepare the BasicBlocks for the func body ---===
    	// create BBs' name str
    	Twine* entryBBName = new Twine("entryBB");
    	Twine* forLoopCondBBName = new Twine("forLoopCondBB");
    	Twine* forLoopBodyBBName = new Twine("forLoopBodyBB");
        Twine* forLoopIncBBName = new Twine("forLoopIncBB");
        Twine* endBBName = new Twine("endBB");
    	// create the BasicBlock objects
    	BasicBlock* entryBB = llvm::BasicBlock::Create(M.getContext(), *entryBBName, fooFuncPtr);
    	BasicBlock* forLoopCondBB = llvm::BasicBlock::Create(M.getContext(), *forLoopCondBBName,fooFuncPtr);
    	BasicBlock* forLoopBodyBB = llvm::BasicBlock::Create(M.getContext(), *forLoopBodyBBName, fooFuncPtr);
    	BasicBlock* forLoopIncBB = llvm::BasicBlock::Create(M.getContext(), *forLoopIncBBName, fooFuncPtr);
        BasicBlock* endBB = llvm::BasicBlock::Create(M.getContext(), *endBBName, fooFuncPtr);
        // Now, we have finished inserting the essential BasicBlock objects into the custom Function object.
        ...
    }
    

    至此,已向Function* fooFuncPtr中植入了构成这个函数的BB块,注意上述BB块创建语句的顺序不能随意变动,要根据IR语句中的排列顺序保持一致,这个调用顺序也就是各个BB块在Function* fooFuncPtr中的排列顺序。

    3.3 创建、使用局部变量

    关键API调用语句整理如下

    IRBuilder<> Builder(entryBB->getContext());
    Builder.SetInsertPoint(entryBB);
    Function::arg_iterator Arg = fooFuncPtr->arg_begin();
    Builder.CreateAlloca();
    Builder.CreateAlignedStore();
    Builder.CreateAlignedLoad();
    Builder.CreateICmpSLT();
    Builder.CreateBr();
    Builder.CreateCondBr();
    Builder.CreateNSWAdd();
    Builder.CreateSExtOrTrunc();
    Builder.CreateInBoundsGEP();
    Builder.CreateRet();
    

    现在自定义foo函数的基本框架已经从宏观上搭建起来,接着就是要对每个BB块填充对应IR指令,此环节需要通过一个IRBuilder<> Builder对象来辅助完成,调用此对象的各种CreateXXX()方法即可创建出对应的IR指令对象,这也是现在构建IR指令的推荐方法,而过去借助各种XXXInst类对象及其成员函数来创建相应IR指令。

    创建局部变量是由alloca指令完成,单独提出来是因为在实际编程中发现,由用户自定义创建一些局部变量供后续使用时存在一个原因不明的bug,即创建自定义局部变量的alloca指令如果不位于Function下的第一个BB块内,则植入此alloca指令的语句会引起Pass模块的执行报错。

    这个现象需要引起重视的场景是假设我们希望对目标程序段的中间部分植入复杂谓词运算提高程序的复杂度,而其中需要用到一些局部变量来辅助运算,那么这些局部变量的创建alloca语句应植入到当前Function的首个BB块内(在OLLVM中进行了验证,LLVM 9.0有待测试)。

    这里clang生成的IR语句中所有alloca都位于第一个BB块内,只需模仿即可,IRBuilder<> Builder对象创建一次后可反复使用,一个BB块以一条终止指令结束,常见的终止指令有brret

    void CreateFooFunc(Module &M) {
    	...
    	// Now, we have finished inserting the essential BasicBlock objects into the custom Function object.
        IRBuilder<> Builder(entryBB->getContext());
        //===--- the first BasicBlock: entryBB ---===
        Builder.SetInsertPoint(entryBB);
    	// allocate stack space for func arguments: a, len
    	// [ %a.addr = alloca i32*, align 8 ]
    	AllocaInst* argVarA = Builder.CreateAlloca(i32_ptr, nullptr, "a");
        argVarA->setAlignment(8);
        // [ %len.addr = alloca i32, align 4 ]
        AllocaInst* argVarLen = Builder.CreateAlloca(i32, nullptr, "len");
        argVarLen->setAlignment(4);
        // allocate stack space for local vars: sum, i
        // [ %sum = alloca i32, align 4 ]
        AllocaInst* VarSum = Builder.CreateAlloca(i32, nullptr, "sum");
        VarSum->setAlignment(4);
        // [ %i = alloca i32, align 4 ]
        AllocaInst* VarI = Builder.CreateAlloca(i32, nullptr, "i");
        VarI->setAlignment(4);
    	
    	// store the argu data into the above space
    	Function::arg_iterator Arg = fooFuncPtr->arg_begin();
    	// the first argu value: a
    	Value* argAVal = &(*Arg);
    	// [ store i32* %a, i32** %a.addr, align 8 ]
    	Builder.CreateAlignedStore(argAVal, argVarA, 8);
        ++Arg;
    	// the second argu value: len
    	Value* argLenVal = &(*Arg);
    	// [ store i32 %len, i32* %len.addr, align 4 ]
    	Builder.CreateAlignedStore(argLenVal, argVarLen, 4);
    	
    	// initialize the local var
    	// [ store i32 0, i32* %sum, align 4 ]
    	Builder.CreateAlignedStore(zero, VarSum, 4);
    	// [ store i32 0, i32* %i, align 4 ]
        Builder.CreateAlignedStore(zero, VarI, 4);
    
    	// add the terminator inst: br
    	// [ br label %7]
        Builder.CreateBr(forLoopCmpBB);
    	......
    }
    

    至此,在首个BB块中已完成相关局部变量的创建工作,并且上述代码已经示范了如何逐条构建自定义函数的指令,其他BB块的构建仿照此过程即可,关键在于调用IRBuilder相应的CreateXX方法。

    一份植入foo函数的完整代码示例如下,而实际开发中还可能需要针对复杂的数据结构、程序结构对应采取不同的实现策略,本文整理了一些常见的实现方式参见后面几个小点:

    void CreateFooFunc(Module &M) {
    // create the definition of foo func in the Module M
    	//===---- prepare the type var and constant nums to simplify the codes ---===
    	IntegerType* i32 = llvm::IntegerType::get(M.getContext(), 32);			// i32 type
    	PointerType* i32_ptr = llvm::IntegerType::getInt32PtrTy(M.getContext());		// i32* type
    	Constant* zero = llvm::ConstantInt::get(M.getContext(), llvm::APInt(32, 0, true));
    	//===--- create the argu list (i32*, i32)---===
    	std::vector<Type*> argsList;
    	argsList.push_back(i32_ptr);			// i32* type
        argsList.push_back(i32);
    	//===--- declare the func prototype in the Module M ---===
    	FunctionType* funcDef = FunctionType::get(i32, argsList, false);
    	//===--- create Function* object for foo func ---===
    	// create name string "foo"
    	std::ostringstream oss;
    	oss << "foo" ;
    	std::string funcNameStr = oss.str();
    	// create func ptr
    	Function* fooFuncPtr = Function::Create(funcDef, Function::ExternalLinkage, funcNameStr, &M);
    
    	//===--- prepare the BasicBlocks for the func body ---===
    	// create BBs' name str
    	Twine* entryBBName = new Twine("entryBB");
    	Twine* forLoopCondBBName = new Twine("forLoopCondBB");
    	Twine* forLoopBodyBBName = new Twine("forLoopBodyBB");
        Twine* forLoopIncBBName = new Twine("forLoopIncBB");
        Twine* endBBName = new Twine("endBB");
    	// create the BasicBlock objects
    	BasicBlock* entryBB = llvm::BasicBlock::Create(M.getContext(), *entryBBName, fooFuncPtr);
    	BasicBlock* forLoopCondBB = llvm::BasicBlock::Create(M.getContext(), *forLoopCondBBName,fooFuncPtr);
    	BasicBlock* forLoopBodyBB = llvm::BasicBlock::Create(M.getContext(), *forLoopBodyBBName, fooFuncPtr);
    	BasicBlock* forLoopIncBB = llvm::BasicBlock::Create(M.getContext(), *forLoopIncBBName, fooFuncPtr);
        BasicBlock* endBB = llvm::BasicBlock::Create(M.getContext(), *endBBName, fooFuncPtr);
    
        IRBuilder<> Builder(entryBB->getContext());
        //===--- the first BasicBlock: entryBB ---===
        Builder.SetInsertPoint(entryBB);
    	// allocate stack space for func arguments: a, len
    	AllocaInst* argVarA = Builder.CreateAlloca(i32_ptr, nullptr, "a");
        argVarA->setAlignment(8);
        AllocaInst* argVarLen = Builder.CreateAlloca(i32, nullptr, "len");
        argVarLen->setAlignment(4);
        // allocate stack space for local vars: sum, i
        AllocaInst* VarSum = Builder.CreateAlloca(i32, nullptr, "sum");
        VarSum->setAlignment(4);
        AllocaInst* VarI = Builder.CreateAlloca(i32, nullptr, "i");
        VarI->setAlignment(4);
    	
    	// store the argu data into the above space
    	Function::arg_iterator Arg = fooFuncPtr->arg_begin();
    	// the first argu value: a
    	Value* argAVal = &(*Arg);
    	Builder.CreateAlignedStore(argAVal, argVarA, 8);
        ++Arg;
    	// the second argu value: len
    	Value* argLenVal = &(*Arg);
    	Builder.CreateAlignedStore(argLenVal, argVarLen, 4);
    	
    	// initialize the local var
    	Builder.CreateAlignedStore(zero, VarSum, 4);
        Builder.CreateAlignedStore(zero, VarI, 4);
    
    	// add the terminator inst: br
        Builder.CreateBr(forLoopCmpBB);
    
    	//===--- the second BasicBlock: forLoopCondBB ---===
    	Builder.SetInsertPoint(forLoopCondBB);
    	Value* IVal_1 = Builder.CreateAlignedLoad(VarI, 4);
        Value* LenVal_1 = Builder.CreateAlignedLoad(argVarLen, 4);
        Value* BrCond_1 = Builder.CreateICmpSLT(IVal_1, LenVal_1);
        Builder.CreateCondBr(BrCond_1, forLoopBodyBB, endBB);
    
    	//===--- the thrid BasicBlock: forLoopBodyBB ---===
    	Builder.SetInsertPoint(forLoopBodyBB);
    	Value* AVal_1 = Builder.CreateAlignedLoad(argVarA, 8);
        Value* IVal_2 = Builder.CreateAlignedLoad(VarI, 4);
        Value* tmp_1 = Builder.CreateSExtOrTrunc(IVal_2, i64);
        Value* elePtr_1 = Builder.CreateInBoundsGEP(i32, AVal_1, tmp_1);
        Value* eleVal_1 = Builder.CreateAlignedLoad(elePtr_1, 4);
        Value* SumVal_1 = Builder.CreateAlignedLoad(VarSum, 4);
        Value* tmp_2 = Builder.CreateNSWAdd(SumVal_1, eleVal_1);
        Builder.CreateAlignedStore(tmp_2, VarSum, 4);
        Builder.CreateBr(forLoopIncBB);
    
    	//===--- the fourth BasicBlock: forLoopIncBB ---===
    	Builder.SetInsertPoint(forLoopIncBB);
    	Value* IVal_3 = Builder.CreateAlignedLoad(VarI, 4);
    	Value* tmp_3 = Builder.CreateNSWAdd(IVal_3, one);
    	Builder.CreateAlignedStore(tmp_3, VarI, 4);
    	Builder.CreateBr(forLoopCondBB);
    	
    	//===--- the fifth BasicBlock: forLoopIncBB ---===	
    	Builder.SetInsertPoint(endBB);
    	Value* SumVal_2 = Builder.CreateAlignedLoad(VarSum, 4);
    	Builder.CreateRet(SumVal_2);
    }
    

    此外,还有一个值得考虑的问题,就是植入Module后的foo函数如何在其他代码中被调用,与之类似地,前面谈到为了辅助运算而在第一个BB块内植入了部分局部变量,这些变量仍然可以仿照上述代码调用CreateXXX()方法实现alloca指令开辟空间,但开辟空间后生成的变量指针则应如何保存并应用到Function后续环境中,这些问题我计划单写一篇博客来介绍一种处理方法。

    3.4 创建、使用数组

    关键API整理如下

    Builder.CreateInBoundsGEP();
    Builder.CreateGEP();
    

    这是访问(多维)数组元素、结构体成员的两个重要指令,其直接作用就是提取被访问对象的内存地址,而提取被访问对象的内容则还需要搭配一条Load指令加载内存地址对应的内容,详细说明可参考LLVM官方手册介绍(http://llvm.org/docs/GetElementPtr.html#introduction),这里直接给出示例代码与说明。

    一维数组

    假设有数组 int a[10] = {0},现需要访问下标为3的元素值(即a[3]),使用IRBuilder.CreateInBoundsGEP()方法,则代码如下:

    // argVarArr contains the array a[10]'s pointer
    Value* arrVal = Builder.CreateAlignedLoad(argVarArr, 8);
    // index value
    Constant* three = llvm::ConstantInt::get(M.getContext(), llvm::APInt(32, 3, true));
    // set the element's type: i32
    Type* eleTy = llvm::Type::getInt32Ty(M.getContext());
    // get the element a[3]'s pointer
    // [ %arrayidx = getelementptr inbounds i32, i32* %a, i32 3 ]
    Value* elePtr = Builder.CreateInBoundsGEP(eleTy, arrVal, three);
    // get the element a[3]'s value
    // [ %4 = load i32, i32* %arrayidx, align 4 ]
    Value* eleVal = Builder.CreateAlignedLoad(elePtr, 4);
    

    注意到,这里索引值采用了 i32 型数值3,如果观察clang输出的 .ll 内容,则会发现clang在生成GEP指令时一般将此数值处理成 i64型,但这个索引值使用 i32 或 i64 型数值都可以。

    使用IRBuilder.CreateGEP()方法则稍微麻烦,需要准备两个索引值,内存地址偏移量的起始仍然是数组指针变量(arrVal)所指向位置,第一个索引值为0,这个偏移量令下一个索引值的起始位置以 arrVal + 0 开始,LLVM手册中引入此索引的原因做出了详细说明,参见链接 http://llvm.org/docs/GetElementPtr.html#introduction,此处不再赘述;第二个索引值就是真正的要访问的数组元素下标值。

    二维数组

    处理多维数组方法同一维数组基本一致,以二维数组为例,假设有数组 int a[3][3],需要获取元素 a[1][2] 的数值,需要分成两步来完成,第一步先获取 a[1]* 指针,第二步基于该指针传入下标值 2 来获取 a[1][2] 的内容,此过程较为麻烦的地方在于要准备好数组每下一层的元素指针类型,供下层GEP指令使用,示例代码如下

    Value* arrVal = Builder.CreateAlignedLoad(argVarArr, 8);
    // index value
    Constant* zero = llvm::ConstantInt::get(M.getContext(), llvm::APInt(64, 0, true));
    Constant* one = llvm::ConstantInt::get(M.getContext(), llvm::APInt(32, 1, true));
    Constant* two = llvm::ConstantInt::get(M.getContext(), llvm::APInt(32, 2, true));
    Constant* three = llvm::ConstantInt::get(M.getContext(), llvm::APInt(32, 3, true));
    // types
    IntegerType* i32 = llvm::IntegerType::get(M.getContext(), 32);
    ArrayType* int_3 = llvm::ArrayType::get(i32, 3);
    ArrayType* int_3_3 = llvm::ArrayType::get(int_3, 3);
    Type* eleTy = llvm::Type::getInt32Ty(M.getContext());
    // get the ptr of a[1]*
    Value* indexList[2] = {zero, one};
    Value* a_1_ptr = Builder.CreateInBoundsGEP(int_3_3, arrVal, indexList);
    // get the ptr of a[1][2]
    Value* indexList[1] = two;
    Value* ele_ptr = Builder.CreateInBoundsGEP(int_3_3, a_1_ptr, indexList);
    // get the value of a[1][2]
    Value* ele_val = Builder.CreateAlignedLoad(ele_ptr, 4);
    

    如果是更高维的数组结构,则仿照上述过程依次提取每一层的指针再传入下一层索引值,重复这个过程直到最后一层取得被访问元素的内容。

    结构体

    结构体的处理方法也基本一致,稍微复杂的情况是结构体内部嵌套其他复杂数据结构,假设有结构体如下:

    struct S{
    	int a[3];
    	float b;
    };
    
    struct S ss = {{1,2,3}, 2.0};
    

    假设要访问结构体的成员ss.a[1]数值,则取地址访问数值的顺序为:

    1. index[2] = {0, 0},a_3_arrPtr = CreateInBoundsGEP(StructType, ssPtr, index)
    2. index[2] = {0, 1},a_3_elePtr = CreateInBoundsGEP(Int_3, a_3_arrPtr, index)
    3. 取值,a_3_eleVal = CreateAlignedLoad(a_3_elePtr, 4)

    3.5 创建、使用全局变量

    3.5.1 创建

    假设要创建全局变量如下:

    int a;
    float b;
    int c[10] = {0};
    char d[] = "abc";
    

    则在代码中可使用如下语句进行创建:

    // types
    IntegerType* i32 = llvm::IntegerType::get(M.getContext(), 32);
    Type* floatTy = llvm::Type::getFloatTy(M.getContext());
    ArrayType* int_10 = llvm::ArrayType::get(i32, 10);
    // var names
    Twine * varAName = new Twine("a");
    Twine * varBName = new Twine("b");
    Twine * varCName = new Twine("c");
    Twine * varDName = new Twine("d");
    // initial values
    Constant* zero = llvm::ConstantInt::get(M.getContext(), llvm::APInt(32, 0, true));
    Constant* ten = llvm::ConstantInt::get(M.getContext(), llvm::APInt(32, 10, true));
    // create global vars
    llvm::IRBuilder<> Builder(module.getContext());
    GlobalVariable * a = new GlobalVariable(M, i32, false,
              GlobalValue::CommonLinkage, nullptr,
              *varAName);
    GlobalVariable * b = new GlobalVariable(M, floatTy, false,
              GlobalValue::CommonLinkage, nullptr,
              *varBName);
    GlobalVariable * c = new GlobalVariable(M, int_10, false,
              GlobalValue::CommonLinkage, nullptr,
              *varCName);
    ConstantAggregateZero* const_zero = ConstantAggregateZero::get(int_10);
    c->setInitializer(const_zero);
    Value* d = Builder.CreateGlobalStringPtr(StringRef("abc"), varDName);
    

    3.5.2 使用

    对上述存放全局变量对象的Pass模块变量使用Load操作加载其对应内容即可取得全局变量的值对象,全局数组元素值的取用仍然采用GEP指令即可实现,此处不再赘述:

    llvm::IRBuilder<> Builder(module.getContext());
    Value* aVal = Builder.CreateAlignedLoad((Value *)a, 4);
    Value* bVal = Builder.CreateAlignedLoad((Value *)b, 4);
    

    3.6 for 循环结构的实现

    由前面foo函数移植示例可知,for循环结构会被clang在IR层处理成三段式BB块结构,基本跳转关系如下图所示,其中For.Body BB位置可能不止一个BB块,内部结构由循环体内部代码控制:
    在这里插入图片描述
    因此移植for循环体时至少要先在Function内依次开辟3个BB块,然后再在BB块内植入具体的每条Instruction,此过程的流程仍然遵循开头提到的移植流程,这里给出一份主要语句编写的示例代码:

    Twine* forLoopCondBBName = new Twine("forLoopCondBB");
    Twine* forLoopBodyBBName = new Twine("forLoopBodyBB");
    Twine* forLoopIncBBName = new Twine("forLoopIncBB");
    Twine* xxxBBName = new Twine("xxxBB");
    // 依次开辟 for 循环体所需要的BB块,xxxBB只作为示范,无实际意义
    BasicBlock* forLoopCondBB = llvm::BasicBlock::Create(M.getContext(), *forLoopCondBBName,fooFuncPtr);
    BasicBlock* forLoopBodyBB = llvm::BasicBlock::Create(M.getContext(), *forLoopBodyBBName, fooFuncPtr);
    BasicBlock* forLoopIncBB = llvm::BasicBlock::Create(M.getContext(), *forLoopIncBBName, fooFuncPtr);
    BasicBlock* xxxBB = llvm::BasicBlock::Create(M.getContext(), *xxxBBName, fooFuncPtr);
    // 开始给各个BB块植入指令
    // forLoopCondBB
    Builder.SetInsertPoint(forLoopCondBB);
    ...
    BrCond = ...
    Builder.CreateCondBr(BrCond, forLoopBodyBB, xxxBB);
    
    // forLoopBodyBB
    Builder.SetInsertPoint(forLoopBodyBB);
    ....
    Builder.CreateBr(forLoopIncBB);
    
    // forLoopIncBB
    Builder.SetInsertPoint(forLoopIncBB);
    ...
    Builder.CreateBr(forLoopCondBB);
    

    3.7 while 循环结构的实现

    while循环又分为while(…){…}do{…}while(…);两类,这两类经clang处理后得到的BB块结构有所差异。假设有数组循环加法运算代码如下:

    // while{...} loop
    void foo1(int* a, int len) {
        int i = 0;
        int sum = 0;
        while (i<len) {
            sum += a[i];
            ++i;
        } 
    }
    
    // do{...}while loop
    void foo2(int* a, int len) {
        int i = 0;
        int sum = 0;
        do {
            sum += a[i];
            ++i;
        } while (i<len);
    }
    

    对于while(…){…},其BB块结构如下:
    在这里插入图片描述
    编写while(…){…}的主体代码如下:

    Twine* whileLoopCondBBName = new Twine("whileLoopCondBB");
    Twine* whileLoopBodyBBName = new Twine("whileLoopBodyBB");
    Twine* xxxBBName = new Twine("xxxBB");
    
    BasicBlock* whileLoopCondBB = llvm::BasicBlock::Create(M.getContext(), *whileLoopCondBBName,fooFuncPtr);
    BasicBlock* whileLoopBodyBB = llvm::BasicBlock::Create(M.getContext(), *whileLoopBodyBBName, fooFuncPtr);
    BasicBlock* xxxBB = llvm::BasicBlock::Create(M.getContext(), *xxxBBName, fooFuncPtr);
    // whileLoopCondBB
    Builder.SetInsertPoint(whileLoopCondBB);
    ...
    BrCond = ...
    Builder.CreateCondBr(BrCond, whileLoopBodyBB, xxxBB);
    
    // whileLoopBodyBB
    Builder.SetInsertPoint(whileLoopBodyBB);
    ....
    Builder.CreateBr(whileLoopCondBB);
    

    对于do{…}while(…);,其BB块结构如下,与前面的不同就是循环体与判定部分的前后顺序进行了互换:
    在这里插入图片描述
    编写do{…}while(…);的主体代码如下:

    Twine* xxxBBName = new Twine("xxxBB");
    Twine* whileLoopBodyBBName = new Twine("whileLoopBodyBB");
    Twine* whileLoopCondBBName = new Twine("whileLoopCondBB");
    Twine* yyyBBName = new Twine("yyyBB");
    
    BasicBlock* xxxBB = llvm::BasicBlock::Create(M.getContext(), *xxxBBName, fooFuncPtr);
    BasicBlock* whileLoopBodyBB = llvm::BasicBlock::Create(M.getContext(), *whileLoopBodyBBName, fooFuncPtr);
    BasicBlock* whileLoopCondBB = llvm::BasicBlock::Create(M.getContext(), *whileLoopCondBBName,fooFuncPtr);
    BasicBlock* yyyBB = llvm::BasicBlock::Create(M.getContext(), *yyyBBName, fooFuncPtr);
    // xxxBB
    Builder.SetInsertPoint(xxxBB);
    ...
    Builder.CreateBr(whileLoopBodyBB);
    // whileLoopBodyBB
    Builder.SetInsertPoint(whileLoopBodyBB);
    ....
    Builder.CreateBr(whileLoopIncBB);
    // whileLoopCondBB
    Builder.SetInsertPoint(whileLoopCondBB);
    ...
    BrCond = ...
    Builder.CreateCondBr(BrCond, whileLoopBodyBB, yyyBB);
    

    3.8 if else 结构的实现

    假设有代码如下:

    int cmp(int a, int b) {
    	if (a > b)
    		return 1;
    	else if (a == b)
    		return 0;
    	else
    		return -1;
    }
    

    其中BB块的结构大致如下,else if 的分支则相当于多个 if … else … 的组合嵌套:
    在这里插入图片描述
    编写if(…){…}else{…}主体代码如下:

    Twine* ifCondBBName = new Twine("ifCondBB");
    Twine* ifBodyBBName = new Twine("ifBodyBB");
    Twine* elseBodyBBName = new Twine("elseBodyBB");
    Twine* xxxBBName = new Twine("xxxBB");
    
    BasicBlock* ifCondBB = llvm::BasicBlock::Create(M.getContext(), *ifCondBBName,fooFuncPtr);
    BasicBlock* ifBodyBB = llvm::BasicBlock::Create(M.getContext(), *ifBBName, fooFuncPtr);
    BasicBlock* elseBodyBB = llvm::BasicBlock::Create(M.getContext(), *elseBodyBBName, fooFuncPtr);
    BasicBlock* xxxBB = llvm::BasicBlock::Create(M.getContext(), *xxxBBName, fooFuncPtr);
    
    // ifCondBB
    Builder.SetInsertPoint(ifCondBB);
    ...
    BrCond = ...
    Builder.CreateCondBr(BrCond, ifBodyBB, elseBodyBB);
    // ifBodyBB
    Builder.SetInsertPoint(ifBodyBB);
    ....
    Builder.CreateBr(xxxBB);
    // elseBodyBB
    Builder.SetInsertPoint(elseBodyBB);
    ....
    Builder.CreateBr(xxxBB);
    

    3.9 调用函数

    主要分为两步:

    1. 函数参数准备
    2. 创建调用指令

    假设有调用语句如下

    // int foo(int* a, int len)
    sum = foo(a, 10);
    

    3.9.1 参数准备

    需要借助一个std::vector<Value*>容器收纳要传入函数的参数值,之后再依次将待传入的参数值添加进去,完成参数准备工作,示例代码如下:

    std::vector<Value*> func_args;
    Constant* zero = llvm::ConstantInt::get(M.getContext(), llvm::APInt(32, 0, true));
    Constant* ten = llvm::ConstantInt::get(M.getContext(), llvm::APInt(32, 10, true));
    ArrayType* int_10 = llvm::ArrayType::get(i32, 10);
    Value* indexList[2] = {zero, zero};
    Value* seqAPtr = Builder.CreateInBoundsGEP(int_10, VarSeqA, indexList);
    func_args.push_back(seqAPtr);
    func_args.push_back(ten);
    

    3.9.2 创建调用指令

    调用IRBuilder.CreateCall(…)接口如下:

    Function* fooFuncPtr = module.getOrInsertFunction("foo",...);
    Value* sumVal = Builder.CreateCall(fooFuncPtr, func_args);
    

    还有一种简便写法,直接使用{…}将待传入的参数值直接嵌套起来传入接口函数中:

    Value* sumVal = Builder.CreateCall(fooFuncPtr, {seqAPtr, ten});
    

    至此,函数调用的call指令创建完成。

    展开全文
  • 这一篇论文作者给出了如何利用spi通信协议和CC3200进行通信,借助WIFI的方式将图像数据传输到上位机,进行时时的图像显示。 第二篇 TI_CC3235XSimple_省略_k_Wi_Fi双波段单片解决方案_ 这一篇介绍了关于CC3235

    涉及CC3200开发板的相关文献阅读笔记

    谢谢各位的围观,本人用盛世容颜保障,这篇博客会不断的更新下去。哈哈!

    写这个笔记的目的,是为了方便梳理CC3200借助无线网络完成数据传输的流程。所以我会把重点放在每篇文章的创新点和具体对于CC3200的应用开发上。

    第一篇 CC3200_SimpleLink_WiFi模块的高速图像传输_朱为

    这一篇论文作者给出了如何利用spi通信协议和CC3200进行通信,借助WIFI的方式将图像数据传输到上位机,进行时时的图像显示。

    在这里插入图片描述

    第二篇 TI_CC3235XSimple_省略_k_Wi_Fi双波段单片解决方案_

    这一篇介绍了关于CC3235板子的一些板载的资源,是官方自己出的一篇介绍性文档。

    第三篇 基于CC3200的Wi_Fi无线组网式跌倒检测系统_高天星

    这篇论文提了两个创新点,其一是在CC3200中运行了RTOS操作系统;其二是将无线AP扩展成了移动终端。借助操作系统的好处是方便借助内嵌的底层网络协议来进行高效率的开发,而移动终端的使用,简化了布置网络环境的难度,也是比较适合这个课题对于便携性的硬性要求。采用的网络协议也是TCP协议。

    对于RTOS的使用,是将系统分解为了三个任务(三个线程),其一是采集任务,用于驱动ADC采集传感器数据;其二是网络任务,是用于建立无线AP和CC3200间的网络通信;其三是发送任务,是将采集到的数据借助网络通道发送出去。并且给定了线程的优先级,网络任务优先级最高,发送和采集平级。

    在这里插入图片描述

    缺点:是没有详细谈如何在安卓端实现的数据接受的数据。

    第四篇 基于CC3200的超声波测距_羊日飞

    学到的知识点:CC3200的工作电压从用户手册上可查是3.3V,所以如果传感器的工作电压是5V时,需要考虑分压。而本文中对于5V传感器的处理,是使用了两个1.2兆欧的电阻对输入信号进行了分压,保障了输入高电平不低于2.132V,输入低电平不高于1.148V。

    在这里插入图片描述

    对于网络通信这里采用的是服务端和客户端的方式,将CC3200作为了服务端,而移动设备做了客户端,采用的是HTTP的网络协议,所以需要借助浏览器的网页来完成数据的接收和触发。和上一篇一样,也是将网络通信的建立作为了优先级最高的任务,而后才是传感器数据的采集和发送。

    第五篇 基于CC3200的可穿戴计步器的设计_何枫

    丝毫没有一点点帮助!意义不大。

    第六篇 基于CC3200的可视化低功耗WiFi门铃设计与实现_张晓丹

    • 技术上

      这篇文章使用的是IAR的开发环境,没有在CC3200中运行系统,是裸机执行,整个系统的执行框图如下所示:

    在这里插入图片描述

    作者进行了JPEG图像编码技术研究,无线TCP/UDP通信协议,音频编解码和客户端软件的研究。涉及了硬件和软件的搭建,算得上是一个全栈工程师了。下图是对于语音通信设计的原理框图:

    在这里插入图片描述

    这里在补充一点关于CC3200在WIFI无线例程中库函数的开发一些经验:

    ConfigureSimpleLinkToDefaultState();//快速实现AP或者STA模式的网络连接
    sL_Strat();//用于开启网络处理器
    SwitchToStaMode();//将设备切换到STA模式
    WlanConnect();//连接设备到指定的WIFI网络
    

    为了防止连接异常,在系统中加入了看门狗功能,一旦异常,系统会自动进行复位,提升系统工作的鲁棒性。

    • 行文上

      采用的是和大论文类似的行文格式,在前言之后,对所参考文献进行了逐一分析,通过对比分析,给出了从节能和环保的创新点。

    • 创新点–节能设计

      由于选用的低功耗的主控,所以启用了CC3200芯片中的休眠模式,用TCP的连接状态作为了休眠模式的触发方式,分别设置了30S的延迟时间。

    • 总结篇

      这篇文章比较详细的给出了整个项目涉及到关键的模块设计结构和具体的实现方式,核心创新是充分的发挥了CC3200的资源,并且在门铃这个课题中找到了实际的应用场景。总结一下所涉及到的内部资源:

      • 定时器
      • 看门狗
      • 串口通信
      • IIC通信
      • WiFi模块(TCP or UDP 协议)
      • DMA接口
      • 中断

    第七篇 基于cc3200的无线传输系统的设计_王博

    从这篇文章中最直观的收获是知道了CC3200实现WIFI通信的具体形式,共有三种方式。分别如下:

    • 方式一:

    在这里插入图片描述

    note:这个方式需要将两块开发板分别配置为接入点和站点。可以理解为通信中的半双工模式。

    • 方式二:

    在这里插入图片描述

    note:这个方式是将两块开发板均配置为站点方式,个人理解是这个方式是选择了一个中继,用来协调不同站点的信息传输,可以实现网络形式的信息传输。

    • 方式三:

    在这里插入图片描述

    note:这个方式需要将两块开发板配置为点对点模式。这个方式应该是对第一种方式的优化,等价于通信中的全双工模式。

    另外本文设计课题是将串口通信和WiFi通信结合了起来,从而实现了无线信息的传输任务。无线通信协议选择的是TCP协议,要想实现这个课题任务,作者选择了分三步走,第一步是调试WLAN无线通信;第二步是TCP套接字通信;第三步是UART通信。使用的开发环境是CCS(官方的开发工具)。

    本文选用了第一种方式,并且给出了具体的执行流程图:

    在这里插入图片描述

    在这里插入图片描述

    第八篇 基于MicroPython和CC_省略_的智能家居数据采集与控制系统设计_邓健

    从技术上来看,这篇论文最不同于其他文章的特点是利用MicroPython 3对CC3200实现的开发。课题背景是要实现智能家居中不同硬件采用不同的网络协议来接入互联网,重在解决对于不同通信协议开发上的难度。因为MicroPython集合了不同的网络通信协议,方便用户根据自己的需求,进行二次开发。下面这张图可以理解为对于智能家居的数据流上的传输过程:

    在这里插入图片描述

    另外作者提到了局域网组网方式和单节点接入网络两种方式的区别是:采用单节点方式可以实现大数据流传输,即表现为实现无线的视频流传输。

    针对课题背景,作者实现了网页端和微信端两种人机交互方式,网页端采用的是中国移动的OneNET,而微信端是采用了公众号的方式。

    第九篇 基于单片Wi_Fi_MCU_CC3200的无线串口_郭书军

    这篇文章可以看成是上述八篇文章的老祖宗了,为啥这么说呢?因为本质上不同课题背景下的数据流通和数据交互上都采用的是无线串口这种工作模式。硬件架构:

    在这里插入图片描述

    其中RF电路用来实现较远距离的通信,降低信号延迟,增加传输带宽;而电平转换电路则是为了实现串口部分的数据发送和接收。辅助电路包括晶振电路,供电模块,下载电路,复位电路。

    第十篇 基于无线传输的热电偶测温系统设计_王军

    核心创新点:

    传统的传感器在测量数据时,需要外接电缆,一旦长度过长,会引起信号衰减,传输过程中的强电耦合,布线复杂等问题,故本文使用了无线的方式,一旦采集到的温度值,会被立即缓存起来,而后才会通过无线的方式进行传输。

    那这里可以总结一下之前对于传感器数据的采集演变过程:

    • “原始时代”

    原始温度数据,可能是传感器采集到多少,就是多少,数据的传输过程是使用电缆传输,这样对变化比较迅捷的环境不适宜测量。

    • ”新石器时代“

    随后发现了缓存这个模块,解决了温度变化比较迅捷的情况,随你外界怎么变,我只把数据存下来,随后慢慢读。要是采集的数据量比较多,则增大缓存容量。

    • “工业文明时代”

    发现要远距离传输数据,故选择增加电缆长度,改变线材。但这毕竟是存在物理层面的限制,借助介质传输,信号必然会受到影响。

    • ”5G时代“

    发现增加电缆长度,会影响信号的质量和增加传输延迟。故选择了无线这种方式。这样配合新石器时代的功能,先把采集的数据存到缓存中,然后在借助WiFi的方式将数据传送出来,这样既增加了采集的数据量,也提升了系统的响应速度,还可以无损的传输采集到的数据。所以这篇论文中的最大创新点是借助无线的方式降低了数据的损耗和延迟,目前是在20m的范围内进行的测试。由于要实现阵列化的传感器数据采集,所以采用了FPGA加持逻辑,论文中提到了课题背景是要实现8路热电偶传感器的数据采集。

    数据传输过程:

    这篇文章给出了非常深刻的使用背景,涉及到了传感器信号的调理,采集,编码,传输,显示过程。所以接下来,按照上述的信号传输过程来进行总结学习。下图是完成的系统工作流程图。

    在这里插入图片描述

    • 调理

    在这里插入图片描述

    因为课题背景是实现热电偶信号的测量,所以在输入端的信号调理上采用的是差分类型的运放。其中信号从WD端输入,先经过一次低通滤波器,滤除高频噪声,而后进入到AD8495中,来实现信号的一次放大,采用的差分输入放大;整个信号调理部分共采用了两级放大,第二级中设置了负反馈方式,放大倍数为 -(R4+R6)/ R6。个人感觉这个电路图是有问题的,对于OP200AZ的信号分析,下面这个节点不应该加的,否则这个电路图,没办法工作,反而会产生自激振荡。但如果把这个节点去掉,这个电路就是同向比例放大电路。

    • 采集

    • 编码

    • 传输

    • 显示

    实验设计上

    为了对系统进行验证,做了环境实验,并且选用了标准的铂金属的电阻进行了温度的标定,而后与实际的测量值进行比对,确定测量误差。

    为了体现创新点,专门用热电偶补偿的方式和无线的方式,将数据采集后,运用数据拟合分析的方式和理想的线性曲线进行比对。结果非常的直观,行文方式和实验设计的方式值得学习。

    展开全文
  • 基于多元化战略的概念,动因和类型,梳理了国内外文献中企业多元化战略与企业绩效的关系。 人们发现,多元化战略是否有利于公司绩效还没有确切的答案。 有四种不同的关系:正相关,负相关和无显着相关以及非线性...
  • Related Work写作步骤:一、整理参考文献二、梳理论文结构三、撰写现状部分 论文的研究现状部分,是对目前相关工作的总结与梳理。我按照自己的理解把写作过程分成了以下几步: 一、整理参考文献 最开始的工作,是要...

    论文的研究现状部分,是对目前相关工作的总结与梳理。我按照自己的理解把写作过程分成了以下几步:

    一、整理参考文献

    最开始的工作,是要对手中的参考文献进行归类,首先按照不同方向将参文分成几大类,如下图:

    在这里插入图片描述

    接着再将每个大类的文献,按照方法或者不同任务细分成若干小类,如第一类异常行为识别可以按照使用的特征以及研究任务细分成下述几类:

    在这里插入图片描述

    至此,先将参考文献进行了一个大致的归类,我们可以将相关论文的摘要整理成一个doc文档。这个归类文档是之后撰写现状部分的逻辑展开的主要参考依据;

    二、梳理论文结构

    在开始写之前。首先你要确定你论文的中心是什么,就是你主要的创新点。在确定了以后,要列一个论文的大纲,整个论文大概分为几部分,每一部分要写什么。

    提纲参照之前的一篇blog来写,列提纲的同时,在每个标题下写下一到两句中心句,以体现每一部分着重描写的内容,同时防止在撰写的时候某一部分偏离文章主旨,而导致前后逻辑不顺畅。大纲一般按照上图中的标题列出;

    在这里插入图片描述

    三、撰写现状部分

    当我们弄清整个文章的结构和中心论点以后,就可以开始撰写现状部分;

    按照之前说的归类文档的对相关工作的分类逻辑展开,例如当前论文相关工作主要有三个大类,所以现状部分我们分三段来撰写,同样需要先写总句,搭好框架后,再填充内容。即总分结构。

    注意在撰写的同时不能脱离本文的主要创新点,应该适当的将相关工作和本文工作对比来突出本文方法的优势,或者其他方法与本文方法的联系。

    写一段话举例,粗体字为主要框架:

    (开始,先介绍某任务的一般做法作为总句;接着按照不同的方法展开叙述:)

    异常行为检测:异常行为检测需要提取异常行为特征,特征分为手工特征和深度特征。

    早期工作主要致力于构造可以表达空间和时间域内信息的手工特征,然后将其输入到结构化的模型【文献1】、【文献2】。

    (转折,以方法的优缺点为线索,逐步引入其他相关文献)

    手工特征存在一系列的问题,如???和???,于是有人引入了深度特征【文献3】【文献4】

    相比于外观特征,人体骨架特征是异常行为识别中更重要的特征,它可以??(有什么优点),因此又有新的方法被提出【文献567】

    (转折句,为引出时空定位的相关文献)

    但是,仅仅识别异常行为是不够的,还需要对它定位才能完成检测。

    定位的方法主要分为 时间定位和空间定位(总)

    时间定位主要依靠???【文献9,10,11,12】,我们的方法与上述研究工作不同,我们是???(分)

    空间定位方面,主要是???【文献13,14,15】,我们的方法受到【文献14】的启发,提出了??(分)

    (撰写的过程中要时刻扣题)

    所以在写现状的时候,要牢牢把握本文的核心论点,要能体现相关工作和本文的联系与区别。然后按照上述逻辑展开,总分式地写作,至少不会让故事线显得太混乱。

    展开全文
  • 这是一本综述性的书,把现存的关于游戏化的各种理论和实践的文献内容进行梳理。对于本书,我印象最深的是其第 3 章:「教学游戏化的支撑理论」。该章介绍了「学习的几种招式」,仔细思考之后觉得非常有意思,分享...
  • 在本文中,我们将对这个话题进行一次完整的梳理,包括域创建,制作网络钓鱼内容,绕过垃圾邮件过滤器和电子邮件网关,生成不可检测的有效载荷以及绕过Windows保护(例如AMSI)的注意事项。另外,我们还在这篇文章的...
  • 笔者使用boost::filesystem进行了一些常见操作,希望对其进行梳理,方便自己和其他人;转载请注明:鹏心雕珑 http://blog.csdn.net/qq_25040013 参考文献: http://blog.csdn.net/tujiaw
  • 快速进入核心领域-histcite使用简介

    千次阅读 2018-02-08 21:08:20
    本文将详细介绍 HistCite 的使用方法,结合 Web of Science 和 Endnote ,演示如何在几个小时之内,对某个陌生领域的文献进行宏观的梳理,并快速定位重要文献。前言如果需要对一个陌生领域进行文献调研。我们通常...
  • HistCite 的使用方法

    千次阅读 2017-08-25 21:08:40
    摘要 读文献自然要读精品,在面对一个陌生领域,如何才能以最快速度定位精品文献呢?本文将详细介绍 HistCite...作为科研工作者,我们常常需要对一个陌生领域进行文献调研。我们通常使用 Web of Science(WOS) 或者
  • 摘要 读文献自然要读精品,在面对一个陌生领域,如何才能以最快速度定位精品文献呢?本文将详细介绍 HistCite...作为科研工作者,我们常常需要对一个陌生领域进行文献调研。我们通常使用 Web of Science(WOS) 或者
  • 在回顾和梳理国外相关创业能力研究文献的基础上,文章归纳总结了创业者创业能力的影响因素及其作用机理,并对该领域的未来研究热点进行了展望,以期为提升创业能力的研究和实践提供理论支持和指导。
  • 本文以人口结构转变理论演进为切入点,结合现有分析方法和结论对人口结构转变影响宏观经济运行的传导路径进行了回顾性梳理。结论表明,首先就方法论而言,人口对宏观经济影响的文献已经从早期的总量分析过渡到了如今的...
  • 摘 要 在现代市场经济中,薪酬管理是企业人力资源管理中最主要、最敏感的环节之一,直接影响企业的竞争力[1]....本文以大连港轮驳公司薪酬管理体系为研究对象,首先对薪酬管理的文献进行梳理,其次,对大...
  • 本文试图对模块化设计的文献和产权的文献进行梳理,以创建公司模块化理论的轮廓。 该理论将根据合作方之间的权利划分(被理解为受保护的权力范围)来研究公司和其他组织。 并且它将断言,组织反映了非模块化结构,...
  • 生信读研第一件事绝对是读大量文献,那么如何在PubMed大量的文献中筛选出自己想要并且高质量的呢?...于是想起了二师兄做的presentation,他按照时间将他研究领域的文献找出来进行梳理,有了大概的了解后一切就...
  • http权威指南

    2016-07-19 14:10:06
    而且,为了节省大家寻找参考文献的时间,我们还对很多HTTP应用程序正常工作所必须的、重要的非HTTP技术进行了介绍。在组织得便于使用的附录中,可以找到按照字母排序的首部参考(这些首部构成了最常见的HTTP文本的...
  • 因此,很有必要对这一日益重要的研究领域的新近发展进行综合,并整体呈现出来。作者一次对时间序列分析的相关进展做出详细、全面的梳理与阐述。这些研究进展包括向量自回归、广义矩估计、单位根的经济与统计结果、非...
  • 这样把包括三个数学难解问题在内的、面向单钥制和双钥制加密及相关认证技术的数学基础知识进行了完整的梳理,构成了相对完备的数学知识体系。 《加密与认证技术的数学基础》注重思想方法和技能的训练及培养,可作为...
  • 因此,很有必要对这一日益重要的研究领域的新近发展进行综合,并整体呈现出来。作者一次对时间序列分析的相关进展做出详细、全面的梳理与阐述。这些研究进展包括向量自回归、广义矩估计、单位根的经济与统计结果、非...
  • 梳理了 大数据应用中面临的安全风险和挑战, 研 究了国内外大数据安全相关的法律规 ,分析了大数据安全标 准化需求和目前已有的相关准, 建立 了大数据安全标准体系,并给出化工作建议 ,最终形成本版白皮 书。...
  • 建模的同学应该多多涉猎不同的文献,中文的,外文的最好都阅读以下, 然后自己在草稿纸上画一些思维导图,对这些文献之间的关系进行一定 的梳理,然后结合本问题分析,看看什么地方需要修改,什么地方需要 精简,...
  • 参考文献 403 索引 405 编辑手记 409 设计手记:属于DBA的记事簿 411 序言  推荐序  2.0时代的DBA专家圈子  看了Eygle发来的《Oracle DBA手记》的手稿,感到这本书的写作思路很有些新意。随着数据库技术自动化...

空空如也

空空如也

1 2
收藏数 27
精华内容 10
关键字:

如何进行文献梳理