ARM linxu启动过程分析(三)

2024-04-16 08:32
文章标签 分析 启动 过程 arm linxu

本文主要是介绍ARM linxu启动过程分析(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 Linux内核启动第二阶段:

内核启动第二阶段主要完成的工作有,cpuID检查,machineID(也就是开发板ID)检查,创建初始化页表,设置C代码运行环境,跳转到内核第一个真正的C函数startkernel开始执行。

这一阶段涉及到两个重要的结构体:一个是structproc_info_list主要描述CPU相关的信息,定义在文件include/asm-arm/procinfo.h中,与其相关的函数及变量在文件arch/arm/mm/proc_arm920.S中被定义和赋值。另外一个更重要的结构体就是描述开发板或者说机器信息的结构体structmachine_desc,定义在include/asm-arm/mach/arch.h文件中,其函数的定义和变量的赋值在板极相关文件arch/arm/mach-s3c2410/mach-smdk2410.c中实现,这也是内核移植非常重要的一个文件。

 Kernelstartup entry point.

该阶段一般由前面的解压缩代码调用

进入该阶段要求:MMU= off, D-cache = off, I-cache = dont care,

 r0= 0, r1 = machine nr.

 *This code is mostly position independent, so if you link the kernelat

 *0xc0008000, you call this at __pa(0xc0008000).

 所有的机器ID列表保存在arch/arm/tools/mach-types文件中,在编译时会将这些机器ID按照统一的格式链接到基本内核映像文件vmlinux__arch_info_begin__arch_info_end之间的段中。存储格式定义在include/asm-arm/mach/arch.h文件中的结构体structmachine_desc{}。这两个结构体的内容最终会被连接到基本内核映像vmlinux中的两个段内,分别是*(.proc.info.init)*(.arch.info.init),可以参考下面的连接脚本。

链接脚本:arch/arm/kernel/vmlinux.lds

SECTIONS

{

. = TEXTADDR;

.init : {   /*初始化代码段*/

        _stext= .;

              _sinittext = .;

              *(.init.text)

              _einittext = .;

        __proc_info_begin= .;

              *(.proc.info.init)

       __proc_info_end= .;

       __arch_info_begin= .;

              *(.arch.info.init)

       __arch_info_end= .;

        __tagtable_begin= .;

              *(.taglist.init)

       __tagtable_end= .;

. = ALIGN(16);

        __setup_start= .;

              *(.init.setup)

        __setup_end= .;

        __early_begin= .;

              *(.early_param.init)

        __early_end= .;

        __initcall_start= .;

              *(.initcall1.init)

              *(.initcall2.init)

              *(.initcall3.init)

              *(.initcall4.init)

              *(.initcall5.init)

              *(.initcall6.init)

              *(.initcall7.init)

        __initcall_end= .;

       __con_initcall_start= .;

              *(.con_initcall.init)

        __con_initcall_end= .;

       __security_initcall_start= .;

              *(.security_initcall.init)

       __security_initcall_end= .;

        .= ALIGN(32);

        __initramfs_start= .;

              usr/built-in.o(.init.ramfs)

        __initramfs_end= .;

        .= ALIGN(64);

        __per_cpu_start= .;

              *(.data.percpu)

        __per_cpu_end= .;

#ifndef CONFIG_XIP_KERNEL

        __init_begin= _stext;

        *(.init.data)

        .= ALIGN(4096);

        __init_end= .;

#endif

}

 __INIT //与链接脚本中的初始化代码段标识同义,表示该代码段为初始化代码段

.type      stext, %function

ENTRY(stext)

msrcpsr_c, #PSR_F_BIT | PSR_I_BIT | MODE_SVC

//进入超级权限模式,关中断

bl   __lookup_processor_type          @ r5=procinfo r9=cupid

//进行CPUID检查,并将CPUI相关的procinfo结构在物理地址空间的首地址保存在r10,

*********************************************************

/*从协处理器CP15C0读取CPUID,然后在__proc_info_begin开始的段中进行查找,如果找到,则返回对应处理器相关结构体在物理地址空间的首地址到r5,最后保存在r10中。

由于此时还没有开启MMU,这里读取到的一些地址信息不是物理地址而是虚拟地址所以要进行相关的地址转换。

 *Returns:

 *    r3, r4, r6 corrupted

 *    r5 = proc_info pointer in physical address space

 *    r9 = cpuid */

.type      __lookup_processor_type, %function

__lookup_processor_type:

adr  r3, 3f   

//读取标号3位置的当前运行时地址,adr为相对寻址所以这里读取的是运行时地址

ldmda    r3, {r5, r6, r9}

//加载对应标号的链接时地址到r5,r6,r9,因为r9r3读取的位置相同,所以二者之间的差就是当前运行时地址与链接地址的真正偏移,然后给r5,r6加上这个偏移值,就实现了将链接时地址转换为当前运行时的地址

sub  r3, r3,r9                    @ get offset between virt&phys

add  r5, r5,r3                    @ convert virt addresses to

add  r6, r6,r3                    @ physical address space

//前面是读取相关段的地址,并将其转换为当前可用的运行时地址

mrc p15, 0, r9, c0, c0  //读取处理器ID

1:    ldmia      r5, {r3, r4}   

//procinfo中读取前两个变量的值,并与处理器ID进行比较,可参考下面的结构体

and  r4, r4,r9                    @ mask wanted bits

teq  r3, r4

beq  2f    //如果正确则返回,不正确则偏移到下一个procinfo结构进行查找

add  r5, r5,#PROC_INFO_SZ          @ sizeof(proc_info_list)

cmp r5, r6

blo   1b

mov r5,#0                         @ unknown processor

2:    mov pc, lr

/*include/asm-arm/procinfo.h

#define PROC_INFO_SZ   48

struct proc_info_list {

        unsignedint         cpu_val;

        unsignedint         cpu_mask;

后面内容省略

};    */

//这是一段C语言调用该函数的代码,将会在start_kernel()---àsetup_arch()

--àarch/arm/kernel/setuo.c文件的setup_processor()函数中调用

ENTRY(lookup_processor_type)

stmfd    sp!, {r4 - r6, r9, lr}

bl   __lookup_processor_type

mov r0, r5

ldmfd    sp!, {r4 - r6, r9, pc}

/* Look ininclude/asm-arm/procinfo.h and arch/arm/kernel/arch.h for

 * moreinformation about the __proc_info and __arch_info structures. */

.long      __proc_info_begin  -----àr5

.long      __proc_info_end    ----àr6

3:    .long                         ----àr9

.long      __arch_info_begin

.long      __arch_info_end

**********************************************************

movs     r10, r5                        @ invalid processor (r5=0)?

beq __error_p                          @ yes, error 'p'

bl   __lookup_machine_type           @ r5=machinfo

//进行机器ID检查,并将其对应machine_desc结构的首地址保存在r8中。

*****************************************************************

/*机器ID是由u-boot引导内核是通过thekernel第二个参数传递进来的,现在保存在r1,__arch_info_begin开始的段中进行查找,如果找到,则返回machine对应相关结构体在物理地址空间的首地址到r5,最后保存在r8中。

由于此时还没有开启MMU,这里读取到的一些地址信息不是物理地址而是虚拟地址所以要进行相关的地址转换。

  * linux/include/asm-arm/mach/arch.h

struct machine_desc {

 unsignedint             nr;   architecture number 

无关内容已删除

 };

//从下面这个宏定义可以看出,machine_desc相关的内容被链接到段.arch.info.init

 #defineMACHINE_START(_type,_name)            /

 conststruct machine_desc __mach_desc_##_type  /

  __attribute__((__section__(".arch.info.init")))= {     /

 .nr =MACH_TYPE_##_type,  /

 .name         = _name,

 

#defineMACHINE_END                          /

 };

 * r1 = machine architecture number

 *Returns:

 * r3, r4, r6 corrupted

 * r5 = mach_info pointer in physical address space*/

.type      __lookup_machine_type, %function

__lookup_machine_type:

adr  r3, 3b

//读取标号3位置的当前运行时地址,adr为相对寻址所以这里读取的是运行时地址

ldmia     r3, {r4, r5, r6}

//加载对应标号的链接时地址到r4,r5,r6,因为r4r3读取的位置相同,所以二者之间的差就是当前运行时地址与链接地址的真正偏移,然后给r5,r6加上这个偏移值,就实现了将链接时地址转换为当前运行时的地址

sub  r3, r3,r4                    @ get offset between virt&phys

add  r5, r5,r3                    @ convert virt addresses to

add  r6, r6,r3                    @ physical address space

//前面是读取相关段的地址,并将其转换为当前可用的运行时地址

1:    ldr   r3, [r5, #MACHINFO_TYPE]    //#defineMACHINFO_TYPE            0

//arch_info中读取第一个变量的值,并与u-boot传递的机器ID进行比较,可参考上面的结构体

teq  r3,r1                   @ matches loader number?

beq 2f                        @ found

add  r5, r5,#SIZEOF_MACHINE_DESC   @ next machine_desc

//  include/asm-arm/asm-offset.h

//#defineSIZEOF_MACHINE_DESC 56 /* sizeof(struct machine_desc) */

cmp r5, r6

blo   1b

mov r5,#0                         @ unknown machine

2:    mov pc, lr

//这是一段C语言调用该函数的代码,将会在start_kernel()---àsetup_arch()

--àarch/arm/kernel/setuo.c文件的setup_machine()函数中调用

ENTRY(lookup_machine_type)

stmfd    sp!, {r4 - r6, lr}

mov r1, r0

bl   __lookup_machine_type

mov r0, r5

ldmfd    sp!, {r4 - r6, pc}

*****************************************************************

movs     r8, r5                          @ invalid machine (r5=0)?

beq __error_a                   @ yes, error 'a'

bl   __create_page_tables  //创建内核初始化页表

*****************************************************

创建内核初始化页表部分代码分析:

/* Setup the initial pagetables.  We only setup the barest

 *amount which are required to get the kernel running, which

 *generally means mapping in the kernel code.

 * r8 = machinfo

 * r9 = cpuid

 * r10= procinfo

 *Returns:

 * r0, r3, r5, r6, r7 corrupted

 * r4 = physical page table address    */

.type      __create_page_tables, %function

__create_page_tables:

ldr   r5, [r8,#MACHINFO_PHYSRAM]   //#define MACHINFO_PHYSRAM    4

//r5=S3C2410_SDRAM_PA,,物理内存起始地址

pgtbl      r4, r5            //r4=stext-0x4000=30008000-4000=0x30004000      

/*    .macro    pgtbl, rd, phys

adr /rd, stext  //stext即就是第二阶段的起始地址,因为这里采用伪指令相对寻址所以stext的地址为0x30008000.

sub /rd, /rd, #0x4000

.endm    */

/*#define PROCINFO_MMUFLAGS       8

#define PROCINFO_INITFUNC          12

#define MACHINFO_TYPE                0

#defineMACHINFO_PHYSRAM       4

#defineMACHINFO_PHYSIO          8

#define MACHINFO_PGOFFIO      12

#defineMACHINFO_NAME             16    */

创建16KB一级交换页表

//清零0x30004000------0x30008000

mov r0, r4

mov r3, #0

add  r6, r0, #0x4000  //r60x30008000也就是交换页表的上限

1:    str   r3, [r0], #4

str   r3, [r0], #4

str   r3, [r0], #4

str   r3, [r0], #4

teq  r0, r6

bne  1b

//一共映射4MB空间,足够内核启动使用即可,因为在内核启动第三阶段这些也表将被函数page_init删除。

//proc_info段加载MMU标记到r7

ldr   r7, [r10,#PROCINFO_MMUFLAGS]      // #define PROCINFO_MMUFLAGS 8

mov r6, pc, lsr#20            //vmlinux起始段地址,pc的最高12位。这里为0x300

orr  r3, r7, r6, lsl#20         //0x300<<20|MMU_flags-àr3即,r3为第一个页表描述符

str   r3, [r4, r6,lsl #2]        //r3保存到0x3004c00=r6<<2=0x300<<2=0xc00+r4(页表起始地址),

/*设置kernel直接映射区域TEXTADDR=0xc0008000 */

add  r0, r4, #(TEXTADDR & 0xff000000) >> 18      //0x30004000+((0xc0008000&0xff000000)>>18)=0x30004000+0x3000=0x30007000=r0

str   r3, [r0,#(TEXTADDR & 0x00f00000) >> 18]!

//r3存储到r0+((0xc0008000&0x00f00000)>>18)=0x30007000+0x3000=0x3000a000

add  r3, r3, #1 <<20 //r3=r3+0x100000

str   r3, [r0,#4]!                 // KERNEL + 1MB

add  r3, r3, #1 <<20

str   r3, [r0,#4]!                 @ KERNEL + 2MB

add  r3, r3, #1 <<20

str   r3, [r0,#4]                  @ KERNEL + 3MB

/*映射物理内存起始地址第一MB的内容,因为其中包括了u-boot传递给内核的参数*/

add  r0, r4,#VIRT_OFFSET >> 18

orr  r6, r5, r7//r5=物理内存地址,r7=MMU标记

str   r6, [r0]

 

#ifdef CONFIG_XIP_KERNEL

无用代码,已删除

#endif

#ifdef CONFIG_DEBUG_LL //调试部分代码,此处不做分析

无用代码,已删除

#ifdefined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)

无用代码,已删除

#endif

#ifdef CONFIG_ARCH_RPC

无用代码,已删除

#endif

#endif

mov pc,lr

.ltorg

*****************************************************

-----------------------------------------------------------------------------------------------------------------

/*下面这段时CPU专用的代码,以位置无关的方式访问

 r10= __lookup_machine_type找到的对应CPUxxx_proc_info结构体的首地址

 返回之后CPU将准备打开MMU

r0保存CPU控制寄存器的值。 */

/*include/asm-arm/procinfo.h

#define PROC_INFO_SZ   48

struct proc_info_list {

        unsignedint         cpu_val;

        unsignedint         cpu_mask;

        unsignedlong             __cpu_mmu_flags;

        unsignedlong             __cpu_flush;        

       constchar           *arch_name;

        constchar           *elf_name;

        unsignedint         elf_hwcap;

        constchar           *cpu_name;

        structprocessor    *proc;

        structcpu_tlb_fns *tlb;

        structcpu_user_fns *user;

        structcpu_cache_fns   *cache;

};   

//上述proc_info_list结构体对应于ARM920TCPU的实现部分,这里只是一部分,详细请参考文件: arch/arm/mm/proc_arm920.S

.align

.section ".proc.info.init",#alloc, #execinstr

.type      __arm920_proc_info,#object

__arm920_proc_info:

.long      0x41009200

.long      0xff00fff0

.long  PMD_TYPE_SECT | /

       PMD_SECT_BUFFERABLE| /

        PMD_SECT_CACHEABLE| /

        PMD_BIT4| /

        PMD_SECT_AP_WRITE| /

        PMD_SECT_AP_READ

      b    __arm920_setup

.long      cpu_arch_name

.long      cpu_elf_name

.long      HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB

.long      cpu_arm920_name

.long      arm920_processor_functions

.long      v4wbi_tlb_fns

.long      v4wb_user_fns

#ifndefCONFIG_CPU_DCACHE_WRITETHROUGH

.long      arm920_cache_fns

#else

.long      v4wt_cache_fns

#endif

.size      __arm920_proc_info, . - __arm920_proc_info

*/

ldr  r13, __switch_data      //设置MMU打开之后跳转到的地址

adr lr, __enable_mmu     //

add pc, r10, #PROCINFO_INITFUNC //PROCINFO_INTFUNC=12

//跳转到对应CPU结构体的__cpu_flush函数执行实际上是执行arch/arm/mm/proc_arm920.S文件中的.proc_info_init段的基址+12位置的函数,也就是执行b     __arm920_setup,如下:

*****************************************************************************

__INIT

.type      __arm920_setup, #function

__arm920_setup:

mov r0, #0

mcr p15, 0, r0, c7,c7          @ invalidateI,D caches on v4

mcr p15, 0, r0, c7, c10,4            @drain write buffer on v4

mcr p15, 0, r0, c8,c7          @ invalidateI,D TLBs on v4

mrc p15, 0, r0, c1,c0          @ getcontrol register v4

ldr   r5,arm920_cr1_clear

bic   r0, r0, r5

ldr   r5,arm920_cr1_set

orr  r0, r0, r5

mov pc,lr  //因为前面设置了lr=__enable_mmu ,所以开始执行启动MMU的代码。

.size      __arm920_setup, . - __arm920_setup

.type      arm920_cr1_clear, #object

.type      arm920_cr1_set, #object

arm920_cr1_clear:

.word     0x3f3f

arm920_cr1_set:

.word     0x3135

*************************************************************************

使能MMU

使能MMU之前设置一些普通bit,装载页表地址以及域访问寄存器

.type      __enable_mmu, %function

__enable_mmu:

#ifdef CONFIG_ALIGNMENT_TRAP

orr r0, r0, #CR_A  //执行该段代码,其余的条件编译条件均未定义

#else

bic   r0, r0, #CR_A

#endif

#ifdefCONFIG_CPU_DCACHE_DISABLE

bic   r0, r0, #CR_C

#endif

#ifdefCONFIG_CPU_BPREDICT_DISABLE

bic   r0, r0, #CR_Z

#endif

#ifdefCONFIG_CPU_ICACHE_DISABLE

bic   r0, r0, #CR_I

#endif

mov r5,#(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | /

             domain_val(DOMAIN_KERNEL,DOMAIN_MANAGER) | /

             domain_val(DOMAIN_TABLE,DOMAIN_MANAGER) | /

             domain_val(DOMAIN_IO,DOMAIN_CLIENT))

mcrp15, 0, r5, c3, c0, 0             //设置域访问寄存器c3

mcrp15, 0, r4, c2, c0, 0             //设置页表地址c2

b    __turn_mmu_on

/*使能MMU,这将完全改变可见的内存空间,不能跟踪执行。

r0 = cp#15 control register

   r13= *virtual* address to jump to upon completion

 其它寄存器的值依赖于上面完成的函数调用。 */

.align     5

.type      __turn_mmu_on, %function

__turn_mmu_on:

mov r0, r0

mcr p15, 0, r0, c1, c0,0             @ write control reg

mrc p15, 0, r3, c0, c0,0             @ read id reg

mov r3, r3

mov r3, r3

mov pc,r13  //前面设置的r13=__switch_data,跳转到__switch_data执行。如下面所示,__switch_data首地址存放的是__mmap_switched的地址,所以实际上是跳转到__mmap_switched执行,注意这里MMU已经开启,无需再进行虚拟地址和物理地址的手工转换,完全由MMU来完成。

-----------------------------------------------------------------------------------------------------------------

.type      __switch_data, %object

__switch_data:

.long      __mmap_switched

.long      __data_loc                  @ r4

.long      __data_start               @ r5

.long      __bss_start                 @ r6

.long      _end                           @ r7

.long      processor_id               @ r4

.long      __machine_arch_type  @ r5

.long      cr_alignment              @ r6

.long      init_thread_union + THREAD_START_SP @ sp

下面这段代码执行时,MMU是开启的,是开启MMU之后执行的第一段代码,使用绝对地址访问方式,而且这段代码时位置相关的。

 * r0  = cp#15 control register

 * r1  = machine ID

 * r9  = processor ID

 .type     __mmap_switched, %function

__mmap_switched:

      adr r3, __switch_data + 4

      ldmia     r3!, {r4, r5, r6, r7}

      //r4位置的数据段搬移到r5开始的位置,实际上是将__data_loc数据段搬移到__data_start位置。

cmp r4,r5                          

1:    cmpne    r5, r6

      ldrne     fp, [r4], #4   //fp即就是寄存器r11àargumentpointer

      strne     fp, [r5], #4

      bne 1b

//清零BSS

      movfp, #0                          

1:    cmp r6, r7

      strcc      fp, [r6],#4

      bcc 1b

 

ldmia     r3, {r4, r5, r6, sp}  //r3指向的是.long   processor_id  @ r4

str  r9, [r4]                //保存process_idr4指向的位置

str  r1, [r5]                //保存__machine_arch_typer5指向的位置,

bic  r4, r0, #CR_A                    @ Clear 'A' bit

stmia     r6, {r0, r4}                 @ Save control register values

b    start_kernel   //跳转到start_kernel函数开始执行内核启动第三阶段



这篇关于ARM linxu启动过程分析(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/908286

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

springboot3打包成war包,用tomcat8启动

1、在pom中,将打包类型改为war <packaging>war</packaging> 2、pom中排除SpringBoot内置的Tomcat容器并添加Tomcat依赖,用于编译和测试,         *依赖时一定设置 scope 为 provided (相当于 tomcat 依赖只在本地运行和测试的时候有效,         打包的时候会排除这个依赖)<scope>provided

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57