Linux內核訪問外設I/O--動態映射(ioremap)和靜態映射(map_desc)


本篇文章主要介紹了"Linux內核訪問外設I/O--動態映射(ioremap)和靜態映射(map_desc)",主要涉及到Linux內核訪問外設I/O--動態映射(ioremap)和靜態映射(map_desc)方面的內容,對于Linux內核訪問外設I/O--動態映射(ioremap)和靜態映射(map_desc)感興趣的同學可以參考一下。

【轉】(轉)Linux內核訪問外設I/O資源的方式-靜態映射(map_desc)方式

Linux內核訪問外設I/O資源的方式
Author: Dongas
Date: 08-08-02
我們知道默認外設I/O資源是不在Linux內核空間中的(如sram或硬件接口寄存器等),若需要訪問該外設I/O資源,必須先將其地址映射到內核空間中來,然后才能在內核空間中訪問它。
Linux內核訪問外設I/O內存資源的方式有兩種:動態映射(ioremap)和靜態映射(map_desc)。
一、動態映射(ioremap)方式
動態映射方式是大家使用了比較多的,也比較簡單。即直接通過內核提供的ioremap函數動態創建一段外設I/O內存資源到內核虛擬地址的映射表,從而可以在內核空間中訪問這段I/O資源。
Ioremap宏定義在asm/io.h內:
#define ioremap(cookie,size)? ?? ?? ???__ioremap(cookie,size,0)
__ioremap函數原型為(arm/mm/ioremap.c):
void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);
phys_addr:要映射的起始的IO地址
size:要映射的空間的大小
flags:要映射的IO空間和權限有關的標志
該函數返回映射后的內核虛擬地址(3G-4G). 接著便可以通過讀寫該返回的內核虛擬地址去訪問之這段I/O內存資源。
舉一個簡單的例子: (取自s3c2410的iis音頻驅動)
比如我們要訪問s3c2410平臺上的I2S寄存器, 查看datasheet 知道IIS物理地址為0x55000000,我們把它定義為宏S3C2410_PA_IIS,如下:
#define S3C2410_PA_IIS? ? (0x55000000)
若要在內核空間(iis驅動)中訪問這段I/O寄存器(IIS)資源需要先建立到內核地址空間的映射:
our_card->regs = ioremap(S3C2410_PA_IIS, 0x100);?
if (our_card->regs == NULL) {
? ?? ?? ?err = -ENXIO;
? ?? ?? ?goto exit_err;
}
創建好了之后,我們就可以通過readl(our_card->regs )或writel(value, our_card->regs)等IO接口函數去訪問它。
二、靜態映射(map_desc)方式
下面重點介紹靜態映射方式即通過map_desc結構體靜態創建I/O資源映射表。
內核提供了在系統啟動時通過map_desc結構體靜態創建I/O資源到內核地址空間的線性映射表(即page table)的方式,這種映射表是一種一一映射的關系。程序員可以自己定義該I/O內存資源映射后的虛擬地址。創建好了靜態映射表,在內核或驅動中訪問該I/O資源時則無需再進行ioreamp動態映射,可以直接通過映射后的I/O虛擬地址去訪問它。
下面詳細分析這種機制的原理并舉例說明如何通過這種靜態映射的方式訪問外設I/O內存資源。
內核提供了一個重要的結構體struct machine_desc ,這個結構體在內核移植中起到相當重要的作用,內核通過machine_desc結構體來控制系統體系架構相關部分的初始化。
machine_desc結構體的成員包含了體系架構相關部分的幾個最重要的初始化函數,包括map_io, init_irq, init_machine以及phys_io , timer成員等。
machine_desc結構體定義如下:
struct machine_desc {
? ? /*
? ???* Note! The first four elements are used
? ???* by assembler code in head-armv.S
? ???*/
? ? unsigned int? ?? ???nr;? ?? ???/* architecture number? ? */
? ? unsigned int? ?? ???phys_io;? ? /* start of physical io? ? */
? ? unsigned int? ?? ???io_pg_offst;? ? /* byte offset for io?
? ?? ?? ?? ?? ?? ?? ?? ? * page tabe entry? ? */
? ? const char? ?? ???*name;? ?? ???/* architecture name? ? */
? ? unsigned long? ?? ???boot_params;? ? /* tagged list? ?? ???*/
? ? unsigned int? ?? ???video_start;? ? /* start of video RAM? ? */
? ? unsigned int? ?? ???video_end;? ? /* end of video RAM? ? */
? ? unsigned int? ?? ???reserve_lp0 :1;? ? /* never has lp0? ? */
? ? unsigned int? ?? ???reserve_lp1 :1;? ? /* never has lp1? ? */
? ? unsigned int? ?? ???reserve_lp2 :1;? ? /* never has lp2? ? */
? ? unsigned int? ?? ???soft_reboot :1;? ? /* soft reboot? ?? ???*/
? ? void? ?? ?? ?? ?(*fixup)(struct machine_desc *,
? ?? ?? ?? ?? ?? ?? ?struct tag *, char **,
? ?? ?? ?? ?? ?? ?? ?struct meminfo *);
? ? void? ?? ?? ?? ?(*map_io)(void);/* IO mapping function? ? */
? ? void? ?? ?? ?? ?(*init_irq)(void);
? ? struct sys_timer? ? *timer;? ?? ???/* system tick timer? ? */
? ? void? ?? ?? ?? ?(*init_machine)(void);
};
這里的map_io成員即內核提供給用戶的創建外設I/O資源到內核虛擬地址靜態映射表的接口函數。Map_io成員函數會在系統初始化過程中被調用,流程如下:
Start_kernel -> setup_arch() --> paging_init() --> devicemaps_init()中被調用
Machine_desc結構體通過MACHINE_START宏來初始化。
注:MACHINE_START的使用及各個成員函數的調用過程請參考:
http://blog.chinaunix.net/u2/60011/showart_1010489.html
用戶可以在定義Machine_desc結構體時指定Map_io的接口函數,這里以s3c2410平臺為例。
s3c2410 machine_desc結構體定義如下:
/* arch/arm/mach-s3c2410/Mach-smdk2410.c */
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
? ?? ?? ?? ?? ???* to SMDK2410 */
? ? /* Maintainer: Jonas Dietsche */
? ? .phys_io? ? = S3C2410_PA_UART,
? ? .io_pg_offst? ? = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
? ? .boot_params? ? = S3C2410_SDRAM_PA + 0x100,
? ? .map_io? ?? ???= smdk2410_map_io,
? ? .init_irq? ? = s3c24xx_init_irq,
? ? .init_machine? ? = smdk2410_init,
? ? .timer? ?? ???= &s3c24xx_timer,
MACHINE_END
如上,map_io被初始化為smdk2410_map_io。smdk2410_map_io即我們自己定義的創建靜態I/O映射表的函數。在Porting內核到新開發板時,這個函數需要我們自己實現。
(注:這個函數通常情況下可以實現得很簡單,只要直接調用iotable_init創建映射表就行了,我們的板子內核就是。不過s3c2410平臺這個函數實現得稍微有點復雜,主要是因為它將要創建IO映射表的資源分為了三個部分(smdk2410_iodesc, s3c_iodesc以及s3c2410_iodesc)在不同階段分別創建。這里我們取其中一個部分進行分析,不影響對整個概念的理解。)
S3c2410平臺的smdk2410_map_io函數最終會調用到s3c2410_map_io函數。
流程如下:s3c2410_map_io -> s3c24xx_init_io -> s3c2410_map_io
下面分析一下s3c2410_map_io函數:
void __init s3c2410_map_io(struct map_desc *mach_desc, int mach_size)
{
? ? /* register our io-tables */
? ? iotable_init(s3c2410_iodesc, ARRAY_SIZE(s3c2410_iodesc));
? ? ……
}
iotable_init內核提供,定義如下:?
/*
* Create the architecture specific mappings
*/
void __init iotable_init(struct map_desc *io_desc, int nr)
{
? ? int i;
? ? for (i = 0; i??nr; i++)
? ?? ???create_mapping(io_desc + i);
}
由上知道,s3c2410_map_io最終調用iotable_init建立映射表。
iotable_init函數的參數有兩個:一個是map_desc類型的結構體,另一個是該結構體的數量nr。這里最關鍵的就是struct map_desc。map_desc結構體定義如下:
/* include/asm-arm/mach/map.h */
struct map_desc {
? ? unsigned long virtual;? ? /* 映射后的虛擬地址 */
? ? unsigned long pfn;? ?? ???/* I/O資源物理地址所在的頁幀號 */
? ? unsigned long length;? ? /* I/O資源長度 */
? ? unsigned int type;? ?? ???/* I/O資源類型 */
};
create_mapping函數就是通過map_desc提供的信息創建線性映射表的。
這樣的話我們就知道了創建I/O映射表的大致流程為:只要定義相應I/O資源的map_desc結構體,并將該結構體傳給iotable_init函數執行,就可以創建相應的I/O資源到內核虛擬地址空間的映射表了。
我們來看看s3c2410是怎么定義map_desc結構體的(即上面s3c2410_map_io函數內的s3c2410_iodesc)。
/* arch/arm/mach-s3c2410/s3c2410.c */
static struct map_desc s3c2410_iodesc[] __initdata = {
? ? IODESC_ENT(USBHOST),
? ? IODESC_ENT(CLKPWR),
? ? IODESC_ENT(LCD),
? ? IODESC_ENT(TIMER),
? ? IODESC_ENT(ADC),
? ? IODESC_ENT(WATCHDOG),
};
IODESC_ENT宏如下:
#define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, __phys_to_pfn(S3C24XX_PA_##x), S3C24XX_SZ_##x, MT_DEVICE }
展開后等價于:
static struct map_desc s3c2410_iodesc[] __initdata = {
? ? {
? ?? ???.virtual? ? =? ???(unsigned long)S3C24XX_VA_ LCD),
? ?? ???.pfn? ?? ???=? ???__phys_to_pfn(S3C24XX_PA_ LCD),
? ?? ???.length? ? =? ? S3C24XX_SZ_ LCD,
? ?? ???.type? ? =? ???MT_DEVICE
? ? },
? ? ……
};
S3C24XX_PA_ LCD和S3C24XX_VA_ LCD為定義在map.h內的LCD寄存器的物理地址和虛擬地址。在這里map_desc 結構體的virtual成員被初始化為S3C24XX_VA_ LCD,pfn成員值通過__phys_to_pfn內核函數計算,只需要傳遞給它該I/O資源的物理地址就行。Length為映射資源的大小。MT_DEVICE為I/O類型,通常定義為MT_DEVICE。
這里最重要的即virtual 成員的值S3C24XX_VA_ LCD,這個值即該I/O資源映射后的內核虛擬地址,創建映射表成功后,便可以在內核或驅動中直接通過該虛擬地址訪問這個I/O資源。
S3C24XX_VA_ LCD以及S3C24XX_PA_ LCD定義如下:
/* include/asm-arm/arch-s3c2410/map.h */
/* LCD controller */
#define S3C24XX_VA_LCD? ?? ?? ? S3C2410_ADDR(0x00600000)? ?//LCD映射后的虛擬地址
#define S3C2410_PA_LCD? ?? ?? ???(0x4D000000)? ? //LCD寄存器物理地址
#define S3C24XX_SZ_LCD? ?? ?? ???SZ_1M? ?? ???//LCD寄存器大小
S3C2410_ADDR 定義如下:
#define S3C2410_ADDR(x)? ?? ???((void __iomem *)0xF0000000 + (x))
這里就是一種線性偏移關系,即s3c2410創建的I/O靜態映射表會被映射到0xF0000000之后。(這個線性偏移值可以改,也可以你自己在virtual成員里手動定義一個值,只要不和其他IO資源映射地址沖突,但最好是在0XF0000000之后。)
(注:其實這里S3C2410_ADDR的線性偏移只是s3c2410平臺的一種做法,很多其他ARM平臺采用了通用的IO_ADDRESS宏來計算物理地址到虛擬地址之前的偏移。
IO_ADDRESS宏定義如下:
/* include/asm/arch-versatile/hardware.h */
/* macro to get at IO space when running virtually */
#define IO_ADDRESS(x)? ?? ?? ?? ?(((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000) )
s3c2410_iodesc這個映射表建立成功后,我們在內核中便可以直接通過S3C24XX_VA_ LCD訪問LCD的寄存器資源。
如:S3c2410 lcd驅動的probe函數內
/* Stop the video and unset ENVID if set */
info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
lcdcon1 = readl(S3C2410_LCDCON1); //read映射后的寄存器虛擬地址
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1); //write映射后的虛擬地址
S3C2410_LCDCON1寄存器地址為相對于S3C24XX_VA_LCD偏移的一個地址,定義如下:
/* include/asm/arch-s3c2410/regs-lcd.h */
#define S3C2410_LCDREG(x) ((x) + S3C24XX_VA_LCD)
/* LCD control registers */
#define S3C2410_LCDCON1? ?? ???S3C2410_LCDREG(0x00)
到此,我們知道了通過map_desc結構體創建I/O內存資源靜態映射表的原理了。總結一下發現其實過程很簡單,一通過定義map_desc結構體創建靜態映射表,二在內核中通過創建映射后虛擬地址訪問該IO資源。
三、I/O靜態映射方式應用實例
I/O靜態映射方式通常是用在寄存器資源的映射上,這樣在編寫內核代碼或驅動時就不需要再進行ioremap,直接使用映射后的內核虛擬地址訪問。同樣的IO資源只需要在內核初始化過程中映射一次,以后就可以一直使用。
寄存器資源映射的例子上面講原理時已經介紹得很清楚了,這里我舉一個SRAM的實例介紹如何應用這種I/O靜態映射方式。當然原理和操作過程同寄存器資源是一樣的,可以把SRAM看成是大號的I/O寄存器資源。
比如我的板子在0x30000000位置有一塊64KB大小的SRAM。我們現在需要通過靜態映射的方式去訪問該SRAM。我們要做的事內容包括修改kernel代碼,添加SRAM資源相應的map_desc結構,創建sram到內核地址空間的靜態映射表。寫一個Sram Module,在Sram Module 內直接通過靜態映射后的內核虛擬地址訪問該sram。
第一步:創建SRAM靜態映射表
在我板子的map_des結構體數組(xxx_io_desc)內添加SRAM資源相應的map_desc。如下:
static struct map_desc xxx_io_desc[] __initdata = {
? ? …………
? ? {
? ?? ???.virtual? ? = IO_ADDRESS(XXX _UART2_BASE),
? ?? ???.pfn? ?? ???= __phys_to_pfn(XXX _UART2_BASE),
? ?? ???.length? ?? ???= SZ_4K,
? ?? ???.type? ?? ???= MT_DEVICE
? ? },{
? ?? ???.virtual? ? = IO_ADDRESS(XXX_SRAM_BASE),
? ?? ???.pfn? ?? ???= __phys_to_pfn(XXX_SRAM_BASE),
? ?? ???.length? ?? ???= SZ_4K,
? ?? ???.type? ?? ???= MT_DEVICE
? ? },
};
宏XXX_SRAM_BASE為我板子上SRAM的物理地址,定義為0x30000000。我的kernel是通過IO_ADDRESS的方式計算內核虛擬地址的,這點和之前介紹的S3c2410有點不一樣。不過原理都是相同的,為一個線性偏移, 范圍在0xF0000000之后。
第二步:寫個SRAM Module,在Module中通過映射后的虛擬地址直接訪問該SRAM資源
SRAM Module代碼如下:
/* Sram Testing Module */
……
static void sram_test(void)
{
? ? void * sram_p;
? ? char str[] = "Hello,sram!\n";
? ??
? ? sram_p = (void *)IO_ADDRESS (XXX_SRAM_BASE); /* 通過IO_ADDRESS宏得到SRAM映射后的虛擬地址 */
? ? memcpy(sram_p, str, sizeof(str));? ? //將 str字符數組拷貝到sram內
? ? printk(sram_p);
? ? printk("\n");
}
static int __init sram_init(void)
{
? ? struct resource * ret;
? ??
? ? printk("Request SRAM mem region ............\n");
? ? ret = request_mem_region(SRAM_BASE, SRAM_SIZE, "SRAM Region");
? ??
? ? if (ret ==NULL) {
? ?? ???printk("Request SRAM mem region failed!\n");
? ?? ???return -1;
? ? }
? ??
? ? sram_test();
? ? return 0;
}
static void __exit sram_exit(void)
{
? ? release_mem_region(SRAM_BASE, SRAM_SIZE);? ??
? ??
? ? printk("Release SRAM mem region success!\n");
? ? printk("SRAM is closed\n");
}
module_init(sram_init);
module_exit(sram_exit);
在開發板上運行結果如下:
/ # insmod bin/sram.ko?
Request SRAM mem region ............
Hello,sram!? ?? ?ß 這句即打印的SRAM內的字符串
/ # rmmod sram
Release SRAM mem region success!
SRAM is close
實驗發現可以通過映射后的地址正常訪問SRAM。
最后,這里舉SRAM作為例子的還有一個原因是通過靜態映射方式訪問SRAM的話,我們可以預先知道SRAM映射后的內核虛擬地址(通過IOADDRESS計算)。這樣的話就可以嘗試在SRAM上做點文章。比如寫個內存分配的MODULE管理SRAM或者其他方式,將一些critical的數據放在SRAM內運行,這樣可以提高一些復雜程序的運行效率(SRAM速度比SDRAM快多了),比如音視頻的編解碼過程中用到的較大的buffer等。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/252969.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/252969.shtml
英文地址,請注明出處:http://en.pswp.cn/news/252969.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

點擊顯示隱藏盒子函數

示例&#xff1a;&#xff08;手機導航欄&#xff09; <header> <div class"logo"></div> <p class"text">微蜂傳媒</p> <div class"nav_btn" οnclick"showHide(.dropdown_menu)"></div> …

MyBatis 緩存機制

Mybatis 有兩級緩存&#xff1a; 一級緩存&#xff1a; 也稱為本地緩存&#xff0c;SqlSession級別的緩存。一級緩存是一直開啟的&#xff1b; 與數據庫同一次會話期間查詢到的數據會放在本地緩存中&#xff0c;以后如果需要獲取相同的數據&#xff0c;直接從緩存中拿&#xff…

Android虛擬化引擎VirtualApp探究

2019獨角獸企業重金招聘Python工程師標準>>> 介紹 首先需要說明的是&#xff0c;VirtualApp并不是前些陣子滴滴開源的插件化框架VirtualApk。 VirtualApp是一個更加黑科技的東西&#xff0c;他可以創建一個虛擬空間&#xff0c;你可以在虛擬空間內任意的安裝、啟動和…

揭開全景相機的創業真相

&#xff08;Bubl全景相機&#xff09; 國外一開源&#xff0c;國內就自主。這在VR&#xff08;虛擬現實&#xff09;領域體現的淋漓盡致——Google的Cardborad一開源&#xff0c;國內就有數百家廠商蜂擁做了各種插手機的VR盒子。到了全景相機&#xff0c;這一幕似乎又開始重演…

一個厲害的網站

2019獨角獸企業重金招聘Python工程師標準>>> dromara 發現一個網站&#xff0c;發現上面的開源項目真的都非常厲害誒。 轉載于:https://my.oschina.net/miaojiangmin/blog/2934221

最全VR產業鏈全景圖(必收藏)

http://www.360doc.com/content/16/0324/20/28622037_544974325.shtml

本地計算機綁定域名訪問

我們知道localhost綁定的是本地主機IP&#xff08;127.0.0.1&#xff09;&#xff0c;那么我們能不能自定義綁定本地主機IP地址呢&#xff1f;答案是肯定的&#xff0c;同修改hosts文件&#xff0c;我們可以實現上面的需求。 打開本地C盤&#xff0c;找到Windows文件夾-->Sy…

Tomcat配置及原理文章

同一tomcat實現多端口多域名訪問 tomcat源碼分析(第一篇 從整體架構開始) tomcat源碼分析(第二篇 tomcat啟動過程詳解) tomcat源碼分析(第三篇 tomcat請求原理解析--Connector源碼分析) tomcat源碼分析(第四篇 tomcat請求處理原理解析--Container源碼分析)轉載于:https://www.c…

windwon安裝macaca環境

一 安裝配置java1.安裝java_jdk &#xff0c;安裝過程中順帶一起安裝jre(1)選擇【新建系統變量】--彈出“新建系統變量”對話框&#xff0c;在“變量名”文本框輸入“JAVA_HOME”,在“變量值”文本框輸入JDK的安裝路徑&#xff0c; 如“C&#xff1a;/Java/jdk1.6.0_25”(2)在“…

三星要用Exynos 9芯片打造獨立VR頭顯

【天極網VR虛擬現實頻道】近期有數據顯示&#xff0c;2016年全球VR虛擬現實設備的出貨量達到了630萬臺&#xff0c;其中三星Gear VR以451萬臺出貨量稱霸全球VR市場&#xff0c;占據高達71%的市場份額。不過三星的眼光并不局限于手機VR設備&#xff0c;這家公司正在計劃推出一款…

消息隊列常見的 5 個應用場景

消息隊列中間件是分布式系統中重要的組件&#xff0c;主要解決應用耦合&#xff0c;異步消息&#xff0c;流量削鋒等問題&#xff0c;實現高性能、高可用、可伸縮和最終一致性架構。使用較多的消息隊列有ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ、RocketMQ。 消息隊列應用場…

Leetcode之二叉樹(前200道)

持續更新... github鏈接&#xff1a;https://github.com/x2mercy/Leetcode_Solution 為什么括號200道呢&#xff01;因為準備按照200道這樣的周期刷&#xff0c;每200道刷兩遍&#xff0c;第一遍按難度刷&#xff0c;第二遍按類別刷&#xff01; 先整理binarytree這一類別也是因…

在ARM Linux下使用GPIO模擬SPI時序詳解

Author&#xff1a;楊正 Data&#xff1a;2016.1.1 Mail&#xff1a;yz2012wwgmail.com一、 概述 SPI是英文SerialPeripheral Interface的縮寫&#xff0c;顧名思義就是串行外圍設備接口。SPI是一種高速、全雙工、同步通信總線&#xff0c;標準的SPI有4個引腳&#xff…

git clone時出現 error:inflate:data stream error(incorrect data check)

git clone時出現 error:inflate:data stream error(incorrect data check) fatal:serrious inflate inconsistency fatal:index-pack failed 經了解&#xff0c;此問題是遺留問題&#xff0c;之前是因為公司對gitlab服務器進行數據遷移而引起這種git clone失敗的原因&#xff0…

CentOS 7.5 使用 yum 安裝 Kubernetes 集群(二)

一、安裝方式介紹 1、yum 安裝 目前CentOS官方已經把Kubernetes源放入到自己的默認 extras 倉庫里面&#xff0c;使用 yum 安裝&#xff0c;好處是簡單&#xff0c;壞處也很明顯&#xff0c;需要官方更新 yum 源才能獲得最新版本的軟件&#xff0c;而所有軟件的依賴又不能自己指…

zbb20171108 tomcat 性能優化

原文地址http://www.cnblogs.com/NiceTime/p/6665416.html 1)內存優化(調整配置堆的大小&#xff0c;修改文件&#xff1a;catalina.sh) JAVA_OPTS"-Djava.awt.headlesstrue -Dfile.encodingUTF-8 -server -XX:MinHeapFreeRatio80 -XX:MaxHeapFreeRatio80 -XX:ThreadStack…

深入理解pthread_cond_wait、pthread_cond_signal

man pthread_cond_wait的解釋 LINUX環境下多線程編程肯定會遇到需要條件變量的情況&#xff0c;此時必然要使用pthread_cond_wait()函數。但這個函數的執行過程比較難于理解。 pthread_cond_wait()的工作流程如下&#xff08;以MAN中的EXAMPLE為例&#xff09;&#xff1a;…

LeetCode算法題-Factorial Trailing Zeroes(Java實現)

這是悅樂書的第183次更新&#xff0c;第185篇原創 01 看題和準備 今天介紹的是LeetCode算法題中Easy級別的第42題&#xff08;順位題號是172&#xff09;。給定一個整數n&#xff0c;返回n&#xff01;中的尾隨零數。例如&#xff1a; 輸入&#xff1a;3 輸出&#xff1a;0 說明…

JavaWeb基礎—JS學習小結

JavaScript是一種運行在瀏覽器中的解釋型的編程語言 推薦&#xff1a;菜鳥教程一、簡介js:javascript是基于對象【哪些基本對象呢】和和事件驅動【哪些主要事件呢】的語言&#xff0c;應用在客戶端&#xff08;注意與面向對象的區分&#xff09; js的三大特點&#xff1a;  交…

Asp.Net 設計模式 之 “簡單工廠”模式

主要思想&#xff1a;public static Operation CreateFactory(string ope) { //實例化空父類&#xff0c;讓父類指向子類 Operation op null; switch (ope) { case "": op …