嵌入式軟件工程師面試題目整理(一)


@

嵌入式軟件工程師面試題目整理(一)

Arm有多少32位寄存器?

  ARM處理器共有37個寄存器。它包含31個通用寄存器和6個狀態寄存器。
在這里插入圖片描述

通用寄存器的分類
a.未備份寄存器,包括R0-R7
  對每個未備份寄存器來說,在所有的模式下都是指同一個物理寄存器(例如:Usr下的R0與FIQ下的R0是同一個寄存器)。在異常程序中斷造成模式切換時,由於不同模式使用的是相同的物理寄存器。這可能導致數據遭到破壞。未備份寄存器沒有被系統作為別的用途,任何場合均可采用未備份寄存器。

b.備份寄存器,包括R8-R14
   對於備份寄存器R8-R12來說,除FIQ模式下其它模式均使用相同的物理寄存器。在FIQ模式下R8_fiq,R9_fiq,R10_fiq,R11_fiq,R12_fiq。它有自己的物理寄存器

  對於R13和R14寄存器每種模式都有自己的物理寄存器(System與Usr的寄存器相同)。當異常中斷發生時,系統使用相應模式下的物理寄存器,從而可以避免數據遭到破壞。(用戶模式和系統模式非異常中斷,所以共用R13 R14
  R13也稱為SP堆棧指針。
  R14也稱為LR寄存器(存放當前子程序的返回地址)

c.程序計數器,PC
  PC寄存器存儲指令地址,由於ARM采用流水機制執行指令,故PC寄存器總是存儲下一條指令的地址。

  由於ARM是按照字對齊故PC被讀取后的值的bit[1:0]總是0b00(thumb的bit[0]是0b0)。

  CPSR(當前程序狀態寄存器)可以在任何處理器模式下被訪問
  SPSR(備份程序狀態寄存器)。當特定的異常中斷發生時,這個寄存器用於存放當前程序狀態寄存器的內容。在異常中斷程序退出時,可以用SPSR中保存的值來恢復CPSR。

  由於用戶模式和系統模式不是異常中斷模式,所以它們沒有SPSR。(用戶模式和系統模式的CPSR也是共用的)當在用戶模式或系統模式中訪問SPSR,將會產生不可預知的結果。

ARM處理器共有37個寄存器。其中包括:31個通用寄存器,包括程序計數器(PC)在內。這些寄存器都是32位寄存器。以及6個32位狀態寄存器。但目前只使用了其中12位。ARM處理器共有7種不同的處理器模式,在每一種處理器模式中有一組相應的寄存器組。任意時刻(也就是任意的處理器模式下),可見的寄存器包括15個通用寄存器(R0~R14)、一個或兩個狀態寄存器及程序計數器(PC)。在所有的寄存器中,有些是各模式共用的同一個物理寄存器;有一些寄存器是各模式自己擁有的獨立的物理寄存器。表1列出了各處理器模式下可見的寄存器情況。 

 

表1  各種處理器模式下的寄存器

用戶模式

系統模式

特權模式

中止模式

未定義指令模式

外部中斷模式

快速中斷模式

R0

R0

R0

R0

R0

R0

R0

R1

R1

R1          

R1

R1

R1

R1

R2

R2

R2

R2

R2

R2

R2

R3

R3

R3

R3

R3

R3

R3

R4

R4

R4

R4

R4

R4

R4

R5

R5

R5

R5

R5

R5

R5

R6

R6

R6

R6

R6

R6

R6

R8

R8

R8

R8

R8

R8

R8_fiq

R9

R9

R9

R9

R9

R9

R9_fiq

R10

R10

R10

R10

R10

R10

R10_fiq

R11

R11

R11

R11

R11

R11

R11_fiq

R12

R12

R12

R12

R12

R12

R12_fiq

R13

R13

R13_svc

R13_abt

R13_und

R13_inq

R13_fiq

R14

R14

R14_svc

R14_abt

R14_und

R14_inq

R14_fiq

PC

PC

PC

PC

PC

PC

PC

CPSR

CPSR

CPSR

SPSR_svc

CPSR

SPSR_abt

CPSR

SPSR_und

CPSR

SPSR_inq

CPSR

SPSR_fiq

 

通用寄存器

通用寄存器可以分為下面3類:未備份寄存器(The unbanked registers),包括R0~R7。備份寄存器(The banked registers),包括R8~R14。程序計數器PC,即R15。

未備份寄存器

未備份寄存器包括R0~R7。對於每一個未備份寄存器來說,在所有的處理器模式下指的都是同一個物理寄存器。在異常中斷造成處理器模式切換時,由於不同的處理器模式使用相同的物理寄存器,可能造成寄存器中數據被破壞。未備份寄存器沒有被系統用於特別的用途,任何可采用通用寄存器的應用場合都可以使用未備份寄存器。

備份寄存器

對於備份寄存器R8~R12來說,每個寄存器對應兩個不同的物理寄存器。例如,當使用快速中斷模式下的寄存器時,寄存器R8和寄存器R9分別記作R8_fiq、R9_fiq;當使用用戶模式下的寄存器時,寄存器R8和寄存器R9分別記作R8_usr、R9_usr等。在這兩種情況下使用的是不同的物理寄存器。系統沒有將這幾個寄存器用於任何的特殊用途,但是當中斷處理非常簡單,僅僅使用R8~R14寄存器時,FIQ處理程序可以不必執行保存和恢復中斷現場的指令,從而可以使中斷處理過程非常迅速。對於備份寄存器R13和R14來說,每個寄存器對應6個不同的物理寄存器,其中的一個是用戶模式和系統模式共用的;另外的5個對應於其他5種處理器模式。采用記號R13_<mode>來區分各個物理寄存器:

其中,<mode>可以是下面幾種模式之一:usr、svc、abt、und、irq及fiq。

寄存器R13在ARM中常用作棧指針。在ARM指令集中,這只是一種習慣的用法,並沒有任何指令強制性的使用R13作為棧指針,用戶也可以使用其他的寄存器作為棧指

針;而在Thumb指令集中,有一些指令強制性地使用R13作為棧指針。

每一種異常模式擁有自己的物理的R13。應用程序初始化該R13,使其指向該異常模式專用的棧地址。當進入異常模式時,可以將需要使用的寄存器保存在R13所指的棧中;當退出異常處理程序時,將保存在R13所指的棧中的寄存器值彈出。這樣就使異常處理程序不會破壞被其中斷程序的運行現場。

寄存器R14又被稱為連接寄存器(Link Register,LR),在ARM體系中具有下面兩種特殊的作用:每一種處理器模式自己的物理R14中存放在當前子程序的返回地址。當通過BL或BLX指令調用子程序時,R14被設置成該子程序的返回地址。在子程序中,當把R14的值復制到程序計數器PC中時,子程序即返回。

當異常中斷發生時,該異常模式特定的物理R14被設置成該異常模式將要返回的地址,對於有些異常模式,R14的值可能與將返回的地址有一個常數的偏移量。具體的返回方式與上面的子程序返回方式基本相同。

R14寄存器也可以作為通用寄存器使用。   

程序計數器R15

程序計數器R15又被記作PC。它雖然可以作為一般的通用寄存器使用,但是有一些指令在使用R15時有一些特殊限制。當違反了這些限制時,該指令執行的結果將是不可預料的。

由於ARM采用了流水線機制,當正確讀取了PC的值時,該值為當前指令地址值加8個字節。也就是說,對於ARM指令集來說,PC指向當前指令的下兩條指令的地址。

由於ARM指令是字對齊的,PC值的第0位和第1位總為0。需要注意的是,當使用指令STR/STM保存R15時,保存的可能是當前指令地址值加8字節,也可能保存的是當前指令地址加12字節。到底是哪種方式,取決於芯片具體設計方式。無論如何,在同一芯片中,要么采用當前指令地址加8,要么采用當前指令地址加12,不能有些指令采用當前指令地址加8,另一些指令采用當前指令地址加12。因此對於用戶來說,盡量避免使用STR/STM指令來保存R15的值。當不可避免這種使用方式時,可以先通過一些代碼來確定所用的芯片使用的是哪種實現方式。

對於ARM版本4以及更高的版本,程序必須保證寫入R15寄存器的地址值的bits[1:0]為0b00;否則將會產生不可預知的結果。

對於Thumb指令集來說,指令是半字對齊的。處理器將忽略bit[0],即寫入R15的地址值首先與0XFFFFFFFC做與操作,再寫入R15中。

還有—些指令對於R15的用法有一些特殊的要求。比如,指令BX利用bit[0]來確定是ARM指令,還是Thumb指令。這種讀取PC值和寫入PC值的不對稱的操作需要特別注意。

程序狀態寄存器

CPSR(當前程序狀態寄存器)可以在任何處理器模式下被訪問。它包含了條件標志位、中斷禁止位、當前處理器模式標志以及其他的一些控制和狀態位。每一種處理器模式下都有一個專用的物理狀態寄存器,稱為SPSR(備份程序狀態寄存器)。當特定的異常中斷發生時,這個寄存器用於存放當前程序狀態寄存器的內容。在異常中斷程序退出時,可以用SPSR中保存的值來恢復CPSR。

由於用戶模式和系統模式不是異常中斷模式,所以它們沒有SPSR。當在用戶模式或系統模式中訪問SPSR,將會產生不可預知的結果。

CPSR的格式如下所示。SPSR格式與CPSR格式相同。

31

30

29

28

27

26

7

6

5

4

3

2

1

0

N

Z

C

V

Q

DNM(RAZ)

I

F

T

M4

M3

M2

M1

M0

 

條件標志位

N(Negative)、Z(Zero)、C(Carry)及V(oVerflow)統稱為條件標志位。大部分的ARM指令可以根據CPSR中的這些條件標志位來選擇性地執行。各條件標志位的具體含義如表2所示。

 

表2 CPSR中的條件標志位

標志位  

含  義

N

本位設置成當前指令運算結果的bit[31)的值

當兩個補碼表示的有符號整數運算時,N=I表示運算的結果為負數;N=0表示結果為正數或零

Z

Z=1表示運算的結果為零;Z=0表示運算的結果不為零。

對於CMP指令,Z=1表示進行比較的兩個數大小相等。

下面分4種情況討論C的設置方法:

在加法指令中(包括比較指令CMN),當結果產生了進位,則C=1,表示無符號數運算發生上溢出;其他情況下C=0。

在減法指令中(包括比較指令CMP),當運算中發生借位則C=0表示無符號數運算發生下溢出;其他情況下C=1。

對於包含移位操作的非加/減法運算指令,C中包含最后一次溢出的位數數值。

對於其他非加/減法運算指令,C位的值通常不受影響。

V  

對於加/減法運算指令,當操作數和運算結果為二進制的補碼表示的帶符號數時V=1表示符號位溢出。

通常其他的指令不影響V位,具體可參考各指令的說明。

 

Q標志位

在ARMv5的E系列處理器中,CPSR的bit[27]稱為Q標志位,主要用於指示增強的

DSP指令是否發生了溢出。同樣的SPSR中的bit[27]也稱為Q標志位,用於在異常中斷發生時保存和恢復CPSR中的Q標志位。

在ARM v5以前的版本及ARM v5的非E系列的處理器中,Q標志位沒有被定義。CPSR的bit[27]屬於DNM(RAZ)。

CPSR中的控制位

CPSR的低8位I、F、T及M[4:0]統稱為控制位。當異常中斷發生時這些位發生變化。在特權級的處理器模式下,軟件可以修改這些控制位。

1)      中斷禁止位

當I=1時禁止IRQ中斷。

當F=1時禁止FIQ中斷。

2)      T控制位    

T控制位用於控制指令執行的狀態,即說明本指令是ARM指令,還是Thumb指令。對與不同版本的ARM處理器,T控制位的含義不同。對於ARMv4以及更高版本的T系列的ARM處理器,

T=0表示執行ARM指令。

T=1表示執行Thumb指令。

對於ARMv5以及更高的版本的非T系列的ARM處理器,T控制位含義如下:

T=0表示執行ARM指令。

T=1表示強制下一條執行的指令產生未定義指令中斷。

3)      M控制位

控制位M[4:0]控制處理器模式,具體含義如表3所示。

表3控制位M[4:0] 的含義

M[4:0]

處理器模式

可訪問的寄存器

0b10000

User   

PC,R14一R0,CPSR

0b10001

FIQ   

PC,R14_fiq-R8_flq,R7~R0,CPSR,SPSR_nq

0b10010

1RQ

PC,R14 _irq-R13 _irq,R12一R0,CPSR,SPSR_ irq

0b10011

Supervisor

PC,R14_ svc-R13 _svc,R12~R0,CPSR,SPSR_svc

0b10111

Abort

PC,R14_abt-R13_abt,R12~R0,CPSR,SPSR_abt

0b11011

Undefined

PC,R14_und-R13_und,R12~R0,CPSR,SPSR_ und

     

 

CPSR中的其他位

CPSR中的其他位用於將來ARM版本的擴展。應用軟件不要操作這些位,以免與ARM將來版本的擴展沖突。

Arm2440和6410有什么區別

  1.主頻不同。2440是400M的。6410是533/667M的;(跑安卓操作系統要500MHZ以上)

  2.處理器版本不一樣:2440是arm920T內核,6410是arm1176ZJF內核;

  3.6410在視頻處理方面比2440要強很多。內部視頻解碼器,包括MPEG4等視頻格式;

  4.6410支持WMV9、xvid、mpeg4、h264等格式的硬解碼和編碼;

  5. 6410多和很多擴展接口比如:tv-out、CF卡和S-Video輸出等;

  6. spi、串口、sd接口也比那兩個要豐富;

  7.6410采用的是DDR內存控制器;2440采用的是SDRam內存控制器

  8.6410為雙總線架構,一路用於內存總線、一路用於Flash總線;

  9.6410的啟動方式更加靈活:主要包括SD、Nand Flash、Nor Flash和OneFlash等設備啟動;

  10.6410的Nand Flash支持SLC和MLC兩種架構,從而大大擴大存儲空間;

  11.6410為雙總線架構,一路用於內存總線、一路用於Flash總線;

  12.6410具備8路DMA通道,包括LCD、UART、Camera等專用DMA通道;

  13.6410還支持2D和3D的圖形加速;

  14.2440一些參數:256M NAND FLASH,64M SDRAM,2M NOR FLASH

在這里插入圖片描述 在這里插入圖片描述

CPU,MPU,MCU,SOC,SOPC聯系與差別

  1.CPU(Central Processing Unit),是一台計算機的運算核心和控制核心。CPU由運算器、控制器和寄存器及實現它們之間聯系的數據、控制及狀態的總線構成。差不多所有的CPU的運作原理可分為四個階段:提取(Fetch)、解碼(Decode)、執行(Execute)和寫回(Writeback)。 CPU從存儲器或高速緩沖存儲器中取出指令,放入指令寄存器,並對指令譯碼,並執行指令。所謂的計算機的可編程性主要是指對CPU的編程。

  2.MPU (Micro Processor Unit),叫微處理器(不是微控制器),通常代表一個功能強大的CPU(暫且理解為增強版的CPU吧),但不是為任何已有的特定計算目的而設計的芯片。這種芯片往往是個人計算機和高端工作站的核心CPU。最常見的微處理器是Motorola的68K系列和Intel的X86系列。

  3.MCU(Micro Control Unit),叫微控制器,是指隨着大規模集成電路的出現及其發展,將計算機的CPU、RAM、ROM、定時計數器和多種I/O接口集成在一片芯片上,形成芯片級的芯片,比如51,avr這些芯片,內部除了CPU外還有RAM,ROM,可以直接加簡單的外圍器件(電阻,電容)就可以運行代碼了,而MPU如x86,arm這些就不能直接放代碼了,它只不過是增強版的CPU,所以得添加RAM,ROM

  MCU MPU 最主要的區別就睡能否直接運行代碼。MCU有內部的RAM ROM,而MPU是增強版的CPU,需要添加外部RAM ROM才可以運行代碼。

  4.SOC(System on Chip),指的是片上系統,MCU只是芯片級的芯片,而SOC是系統級的芯片,它既MCU(51,avr)那樣有內置RAM,ROM同時又像MPU(arm)那樣強大的,不單單是放簡單的代碼,可以放系統級的代碼,也就是說可以運行操作系統(將就認為是MCU集成化與MPU強處理力各優點二合一)。

  5.SOPC(System On a Programmable Chip)可編程片上系統(FPGA就是其中一種),上面4點的硬件配置是固化的,就是說51單片機就是51單片機,不能變為avr,而avr就是avr不是51單片機,他們的硬件是一次性掩膜成型的,能改的就是軟件配置,說白點就是改代碼,本來是跑流水燈的,改下代碼,變成數碼管,而SOPC則是硬件配置,軟件配置都可以修改,軟件配置跟上面一樣,沒什么好說的,至於硬件,是可以自己構建的也就是說這個芯片是自己構造出來的,這顆芯片我們叫“白片”,什么芯片都不是,把硬件配置信息下載進去了,他就是相應的芯片了,可以讓他變成51,也可以是avr,甚至arm,同時SOPC是在SOC基礎上來的,所以他也是系統級的芯片,所以記得當把他變成arm時還得加外圍ROM,RAM之類的,不然就是MPU了。

  順便再講一下這個SOPC吧,首先上面講的“白片”一般指的是FPGA或CPLD這類芯片,由於它是可配置的,所以一旦斷電,他的硬件配置就沒了,當然,軟件配置也沒了,什么都沒了,比如把他硬件配置成51單片機,軟件配置為跑流水燈,結果一斷電,這個芯片就什么都不是了,恢復原樣“白片”。

  一般有兩種用法,一是用它來驗證芯片,因為他是可以多次下載配置驗證的,成功后再把這硬件配置下載到一次性的芯片上,如果采用基於hardcopy的SOC則成功率100%,不然每次下載硬件配置驗證用SOC等到你調試出正確的硬件配置,那得燒多少芯片啊,畢竟這些是一次性的,不成功只能成仁--扔掉!跟調試軟件配置一樣,一般軟件調試很多次才能成功的,所以他是驗證技術,行了再將配置配在其他的芯片,第二種方法是,芯片就用這“白片”,然后把配置信息放到flash里,上電后先將這硬件配置信息燒入這“白片”,使其變成自己想要的芯片,然后再調入軟件配置。其中硬件配置可以用quartus軟件編寫,軟件配置可以用NIOS軟件,這都是altera公司的產品,可以去查看。

上拉&下拉&高阻態

在這里插入圖片描述
  上拉就是將不確定的信號通過一個電阻嵌位在高電平,電阻同時起限流作用。下拉同理。

  上拉電阻是用來解決總線驅動能力不足時提供電流的,一般說法是拉電流。下拉電阻是用來吸收電流的,也就是我們通常所說的灌電流。提升電流和電壓的能力是有限的,且弱強只是上拉電阻的阻值不同。

  三態門,是指邏輯門的輸出除有高、低電平兩種狀態外,還有第三種狀態——高阻狀態的門電路。具備這三種狀態的器件就叫做三態(門,總線,...)。

  如果你的設備端口要掛在一個總線上,“必須通過三態緩沖器”。因為在一個總線上同時只能有一個端口作輸出,這時其他端口必須在高阻態,同時“可以輸入這個輸出端口的數據”。所以你還需要有總線控制管理, 訪問到哪端口,那個端口的三態緩沖器才可以轉入輸出狀態,這是典型的三態門應用。 如果在線上沒有兩個以上的輸出設備, 當然用不到三態門。

  高阻態,指的是電路的一種輸出狀態,既不是高電平也不是低電平。高阻態只有電容效應,沒有電阻效應;阻抗很高很高,相當於斷開。如果高阻態再輸入下一級電路的話,對下級電路無任何影響,和沒接一樣,如果用萬用表測的話有可能是高電平也有可能是低電平隨它后面接的東西定

  懸空和高阻態的區別 懸空(浮空,floating):就是邏輯器件的輸入引腳即不接高電平,也不接低電平。由於邏輯器件的內部結構,當它輸入引腳懸空時,相當於該引腳接了高電平。一般實際運用時,引腳不建議懸空,易受干擾。 高阻態:從邏輯器件內部電路結構來說,就是其輸出電阻很大,該狀態即不是高電平,也不是低電平。當三態門處於高阻態時,無論該門的輸入如何變化,都不會對其輸出有貢獻。

串口協議講一講

  串口在嵌入式系統當中是一類重要的數據通信接口,其本質功能是作為 CPU串行設備間的編碼轉換器。當數據從 CPU 經過串行端口發送出去時,字節數據轉換為串行的位;在接收數據時,串行的位被轉換為字節數據。應用程序要使用串口進行通信,必須在使用之前向操作系統提出資源申請要求(打開串口),通信完成后必須釋放資源(關閉串口)。典型地,串口用於 ASCII 碼字符的傳輸。通信使用3根線完成:(1)地線,(2)發送數據線,(3)接收數據線。

  串口通信最重要的參數是波特率、數據位、停止位和奇偶校驗。對於兩個進行通行的端口,這些參數必須匹配:波特率是一個衡量通信速度的參數,它表示每秒鍾傳送的 bit 的個數;數據位是衡量通信中實際數據位的參數,當計算機發送一個信息包,標准的值是 5,7 和 8 位。如何設置取決於你的需求;停止位用於表示單個包的最后一位,典型的值為 1,1.5和 2 位,停止位不僅僅是表示傳輸的結束,並且提供計算機校正時鍾同步的機會;奇偶校驗位是串口通信中一種簡單的檢錯方式有四種檢錯方式——偶、奇、高和低,也可以沒有校驗位

  奇校驗:校核數據完整性的一種方法,一個字節的8個數據位與校驗位(parity bit )加起來之和有奇數個1。校驗線路在收到數后,通過發生器在校驗位填上0或1,以保證和是奇數個1。因此,校驗位是0時,數據位中應該有奇數個1;而校驗位是1時,數據位應該有偶數個1如果讀取數據時發現與此規則不符,CPU會下令重新傳輸數據。 奇/偶校驗(ECC)是數據傳送時采用的一種校正數據錯誤的一種方式,分為奇校驗和偶校驗兩種。 如果是采用奇校驗,在傳送每一個字節的時候另外附加一位作為校驗位,當實際數據中“1”的個數為偶數的時候,這個校驗位就是“1”,否則這個校驗位就是“0”,這樣就可以保證傳送數據滿足奇校驗的要求。在接收方收到數據時,將按照奇校驗的要求檢測數據中“1”的個數,如果是奇數,表示傳送正確,否則表示傳送錯誤。 同理偶校驗的過程和奇校驗的過程一樣,只是檢測數據中“1”的個數為偶數。

RS232和RS485通訊接口有什么區別

  1、傳輸方式不同。 RS-232采取不平衡傳輸方式,即所謂單端通訊. 而RS485則采用平衡傳輸,即差分傳輸方式

  2、傳輸距離不同。RS-232適合本地設備之間的通信,傳輸距離一般不超過20m。而RS-485的傳輸距離為幾十米到上千米

  3、RS-232 只允許一對一通信,而RS-485 接口在總線上是允許連接多達128個收發器。 RS232/RS485,是兩種不同的電氣協議,也就是說,是對電氣特性以及物理特性的規定,作用於數據的傳輸通路上,它並不內含對數據的處理方式。比如,最顯著的特征是:RS232使用3-15v有效電平,而UART,因為對電氣特性並沒有規定,所以直接使用CPU使用的電平,就是所謂的TTL電平(可能在0~3.3V之間)。更具體的,電氣的特性也決定了線路的連接方式,比如RS232,規定用電平表示數據,因此線路就是單線路的,兩根線才能達到全雙工的目的;而RS485, 使用差分電平表示數據,因此,必須用兩根線才能達到傳輸數據的基本要求,要實現全雙工,必需用4根線。但是,無論使用RS232還是RS485,它們與UART是相對獨立的,但是由於電氣特性的差別,必須要有專用的器件和UART接駁,才能完成數據在線路和UART之間的正常流動。

   總結:從某種意義上,可以說,線路上存在的僅僅是電流,RS232/RS485規定了這些電流在什么樣的線路上流動和流動的樣式;在UART那里,電流才被解釋和組裝成數據,並變成CPU可直接讀寫的形式。

IIC時序圖畫一下,IIC有哪些狀態,給一個字節,將它發送出去。IIC有什么注意事項?有沒有用I/O模擬IIC,如果有需要注意什么?

  見前ARM部分

為什么2440的內存起始地址是3后面7個0呢?為什么6410的內存起始地址是5后面7個0呢?

內存映射 (256M SDRAM)
在這里插入圖片描述

內存管理有什么看法?(MMU)

  見之前Linux部分MMU簡介

鎖有哪些?有什么注意事項

自旋鎖
  上廁所,等待,自旋。
  如果線程A持有自旋鎖時間過長,顯然會浪費處理器的時間,降低了系統性能。自旋鎖只適合短期持有,如果遇到需要長時間持有的情況,我們就要換一種方式了(下文的互斥體)。

讀寫鎖
  當臨界區的一個文件可以被同時讀取,但是並不能被同時讀和寫。如果一個線程在讀,另一個線程在寫,那么很可能會讀取到錯誤的不完整的數據讀寫自旋鎖是可以允許對臨界區的共享資源進行並發讀操作的。但是並不允許多個線程並發讀寫操作。如果想要並發讀寫,就要用到了順序鎖

順序鎖
  順序鎖是讀寫鎖的優化版本,讀寫鎖不允許同時讀寫,而使用順序鎖可以完成同時進行讀和寫的操作,但並不允許同時的寫。雖然順序鎖可以同時進行讀寫操作,但並不建議這樣,讀取的過程並不能保證數據的完整性

信號量和自旋鎖區別

信號量
  信號量和自旋鎖有些相似,不同的是信號量會發出一個信號告訴你還需要等多久。因此,不會出現傻傻等待的情況。比如,有100個停車位的停車場,門口電子顯示屏上實時更新的停車數量就是一個信號量。這個停車的數量就是一個信號量,他告訴我們是否可以停車進去。當有車開進去,信號量加一,當有車開出來,信號量減一。

  比如,廁所一次只能讓一個人進去,當A在里面的時候,B想進去,如果是自旋鎖,那么B就會一直在門口傻傻等待。如果是信號量,A就會給B一個信號,你先回去吧,我出來了叫你。這就是一個信號量的例子,B聽到A發出的信號后,可以先回去睡覺,等待A出來。
  
  因此,信號量顯然可以提高系統的執行效率,避免了許多無用功。信號量具有以下特點:
  因為信號量可以使等待資源線程進入休眠狀態,因此適用於那些占用資源比較久的場合。
  因此信號量不能用於中斷中,因為信號量會引起休眠中斷不能休眠
  如果共享資源的持有時間比較短,那就不適合使用信號量了,因為頻繁的休眠、切換線程引起的開銷要遠大於信號量帶來的那點優勢。

  在驅動程序中,當多個線程同時訪問相同的資源時(驅動程序中的全局變量是一種典型的共享資源),可能會引發"競態",因此我們必須對共享資源進行並發控制。Linux內核中解決並發控制的最常用方法是自旋鎖與信號量(絕大多數時候作為互斥鎖使用)。

  自旋鎖與信號量"類似而不類",類似說的是它們功能上的相似性,"不類"指代它們在本質和實現機理上完全不一樣,不屬於一類。

  自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環查看是否該自旋鎖的保持者已經釋放了鎖,"自旋"就是"在原地打轉"。而信號量則引起調用者睡眠,它把進程從運行隊列上拖出去,除非獲得鎖。這就是它們的"不類"。

  但是,無論是信號量,還是自旋鎖,在任何時刻,最多只能有一個保持者,即在任何時刻最多只能有一個執行單元獲得鎖。這就是它們的"類似"。

  鑒於自旋鎖與信號量的上述特點,一般而言,自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用;信號量適合於保持時間較長的情況,會只能在進程上下文使用。如果被保護的共享資源只在進程上下文訪問,則可以以信號量來保護該共享資源,如果對共享資源的訪問時間非常短,自旋鎖也是好的選擇。但是,如 果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理句柄和頂半部即軟中斷),就必須使用自旋鎖

區別總結如下:
  1、由於爭用信號量的進程在等待鎖重新變為可用時會睡眠,所以信號量適用於鎖會被長時間持有的情況。
  2、相反,鎖被短時間持有時,使用信號量就不太適宜了(自旋鎖比較合適),因為睡眠引起的耗時可能比鎖被占用的全部時間還要長。
  3、由於執行線程在鎖被爭用時會睡眠,所以只能在進程上下文中才能獲取信號量鎖,因為在中斷上下文中(使用自旋鎖)是不能進行調度的。
   4、你可以在持有信號量時去睡眠(當然你也可能並不需要睡眠),因為當其它進程試圖獲得同一信號量時不會因此而死鎖,(因為該進程也只是去睡眠而已,而你最終會繼續執行的)。
  5、在你占用信號量的同時不能占用自旋鎖,因為在你等待信號量時可能會睡眠,而在持有自旋鎖時是不允許睡眠的。
   6、信號量鎖保護的臨界區可包含可能引起阻塞的代碼,而自旋鎖則絕對要避免用來保護包含這樣代碼的臨界區,因為阻塞意味着要進行進程的切換,如果進程被切換出去后(鎖定期間被切換出去),另一進程企圖獲取本自旋鎖,死鎖就會發生。
   7、信號量不同於自旋鎖,它不會禁止內核搶占(自旋鎖被持有時,內核不能被搶占),所以持有信號量的代碼可以被搶占,這意味着信號量不會對調度的等待時間帶來負面影響。
  除了以上介紹的同步機制方法以外,還有BKL(大內核鎖),Seq鎖等。
  BKL是一個全局自旋鎖,使用它主要是為了方便實現從Linux最初的SMP過度到細粒度加鎖機制。
  Seq鎖用於讀寫共享數據,實現這樣鎖只要依靠一個序列計數器。
總結:二者區別主要從持有時間使用環境(進程上下文還是中斷上下文),內核搶占

中斷能不能睡眠,為什么?下半部能不能睡眠?

  1、 中斷處理的時候,不應該發生進程切換,因為在中斷context中,唯一能打斷當前中斷handler的只有更高優先級的中斷,它不會被進程打斷,如果在中斷context中休眠,則沒有辦法喚醒它,因為所有的wake_up_xxx都是針對某個進程而言的,而在中斷context中,沒有進程的概念,沒 有一個task_struct(這點對於softirq和tasklet一樣),因此真的休眠了,比如調用了會導致block的例程,內核幾乎肯定會死。

  2、schedule()在切換進程時,保存當前的進程上下文(CPU寄存器的值、進程的狀態以及堆棧中的內容),以便以后恢復此進程運行。中斷發生后,內核會先保存當前被中斷的進程上下文(在調用中斷處理程序后恢復);

  但在中斷處理程序里,CPU寄存器的值肯定已經變化了吧(最重要的程序計數器PC、堆棧SP等),如果此時因為睡眠或阻塞操作調用了schedule(),則保存的進程上下文就不是當前的進程context了.所以不可以在中斷處理程序中調用schedule()。????

  3、2.4內核中schedule()函數本身在進來的時候判斷是否處於中斷上下文:

if(unlikely(in_interrupt()))

BUG();

  因此,強行調用schedule()的結果就是內核BUG,但我看2.6.18的內核schedule()的實現卻沒有這句,改掉了。

  4、中斷handler會使用被中斷的進程內核堆棧,但不會對它有任何影響,因為handler使用完后會完全清除它使用的那部分堆棧,恢復被中斷前的原貌。

  5、處於中斷context時候,內核是不可搶占的。因此,如果休眠,則內核一定掛起。

為什么軟中斷中也不能睡眠

  這個問題實際上是一個老生常談的問題,答案也很簡單,Linux在軟中斷上下文中是不能睡眠的,原因在於Linux的軟中斷實現上下文有可能是中斷上下文,如果在中斷上下文中睡眠,那么會導致Linux無法調度,直接的反應是系統Kernel Panic,並且提示dequeue_task出錯。所以,在軟中斷上下文中,我們不能使用信號量等可能導致睡眠的函數,這一點在編寫IO回調函數時需要特別注意。  在最近的一個項目中,我們在dm-io的callback函數中去持有semaphore訪問競爭資源,導致了系統的kernel panic。其原因就在於dm-io的回調函數在scsi soft irq中執行,scsi soft irq是一個軟中斷,其會在硬中斷發生之后被執行,執行上下文為中斷上下文。

  中斷上下文中無法睡眠的原因大家一定很清楚,原因在於中斷上下文不是一個進程上下文其沒有一個專門用來描述CPU寄存器等信息的數據結構,所以無法被調度器調度。如果將中斷上下文也設計成進程上下文,那么調度器就可以對其進行調度,如果在開中斷的情況下,其自然就可以睡眠了。但是,如果這樣設計,那么中斷處理的效率將會降低。中斷(硬中斷、軟中斷)處理都是些耗時不是很長,對實時性要求很高,執行頻度較高的應用,所以,如果采用一個專門的后台daemon對其處理,顯然並不合適。

   Linux對中斷進行了有效的管理,一個中斷發生之后,都會通過相應的中斷向量表獲取該中斷的處理函數。在Linux操作系統中都會調用do_IRQ這個函數,在這個函數中都會執行__do_IRQ(),__do_IRQ函數調用該中斷的具體執行函數。在執行過程中,該函數通過中斷號找到具體的中斷描述結構irq_desc,該結構對某一具體硬件中斷進行了描述。在irq_desc結構中存在一條鏈表irqaction,這條鏈表中的某一項成員都是一個中斷處理方法。這條鏈表很有意思,其實現了中斷共享,例如傳統的PCI總線就是采用共享中斷的方法,該鏈表中的一個節點就對應了一個PCI設備的中斷處理方法。在PCI設備驅動加載時,都需要注冊本設備的中斷處理函數,通常會調用request_irq這個函數,通過這個函數會構造一個具體的irq action,然后掛接到某個具體irq_desc的action鏈表下,實現中斷處理方法的注冊。在__do_IRQ函數中會通過handle_IRQ_event()函數遍歷所有的action節點,完成中斷處理過程。到目前為止,中斷處理函數do_IRQ完成的都是上半部的工作,也就是設備注冊的中斷服務程序。在中斷上半部中,通常都是關中斷的,基本都是完成很簡單的操作,否則將會導致中斷的丟失。耗時時間相對較長,對實時性要求不是最高的應用都會被延遲處理,都會在中斷下半部中執行。所以,在中斷上半部中都會觸發軟中斷事件,然后執行完畢,退出服務。

   __do_IRQ完成之后,返回到do_IRQ函數,在該函數中調用了一個非常重要的函數irq_exit(),在該函數中調用invoke_softirq(),invoke_softirq調用do_softirq()函數,執行軟中斷的操作。此時,程序的執行環境還是中斷上下文,但是與中斷上半部不同的是,軟中斷執行過程中是開中斷的,能夠被硬中斷而中斷。所以,如果用戶的程序在軟中斷中睡眠,操作系統該如何調度呢?只有kernel panic了。另外,軟中斷除了上述執行點之外,還有其他的執行點,在內核中還有一個軟中斷的daemon處理軟中斷事務,驅動程序也可以自己觸發一個軟中斷事件,並且在軟中斷的daemon上下文中執行。但是硬中斷觸發的事件都不會在這個daemon的上下文中執行,除非修改Linux中的do__IRQ代碼。

  上述對軟中斷的執行做了簡要分析,我對Linux中的硬中斷管理機制做了一些代碼分析,這一塊代碼量不是很大,可移植性非常的好~~建議大家閱讀,對我上述的分析和理解存在什么不同意見,歡迎大家討論。

上下文有哪些?怎么理解?

  見前操作系統部分

死鎖產生的原因及四個必要條件

  以按鍵驅動為例,首先執行的module_init()、module_exit()指定的init函數和exit函數。在init函數中,register_chrdev注冊一個字符設備驅動,class_create創建類的名字,class_device_create創建設備。ioremap對管腳進行地址映射。

  在應用程序中調用open函數,就會執行到底層的open函數,在open函數中完成對相應引腳的配置,在應用層調用read函數讀取的時候,就會調用底層的read,在里面可以讀取按鍵的值或者點亮LED等等,做一些需要做的事情。最后退出程序的時候就會調用exit函數,unregister_chrdev //卸載驅動,class_device_unregister卸載類設備。class_destroy卸載類,iounmap注銷虛擬地址。

LCD驅動如何編寫?觸摸屏驅動如何編寫?

  見之前Linux部分

觸摸屏中斷做了什么,LCD中斷做了什么?

  觸摸屏中斷中啟動ADC轉換,ADC轉換完成后會產生ADC中斷。在ADC中斷中讀取數據.之后啟動定時器,在定時器中斷中判斷是否繼續按下,如果繼續按下就會啟動ADC繼續轉換。

什么是交叉編譯?為什么需要交叉編譯?為什么還要主機編譯

什么是交叉編譯
  在一種計算機環境中運行的編譯程序,能編譯出在另外一種環境下運行的代碼,我們就稱這種編譯器支持交叉編譯。這個編譯過程就叫交叉編譯。簡單地說,就是在一個平台上生成另一個平台上的可執行代碼。這里需要注意的是所謂平台,實際上包含兩個概念:體系結構(Architecture)、操作系統(OperatingSystem)。同一個體系結構可以運行不同的操作系統;同樣,同一個操作系統也可以在不同的體系結構上運行。舉例來說,我們常說的x86 Linux平台實際上是Intel x86體系結構和Linux for x86操作系統的統稱;而x86 WinNT平台實際上是Intel x86體系結構和Windows NT for x86操作系統的簡稱。

   交叉編譯這個概念的出現和流行是和嵌入式系統的廣泛發展同步的。我們常用的計算機軟件,都需要通過編譯的方式,把使用高級計算機語言編寫的代碼(比如C代碼)編譯(compile)成計算機可以識別和執行的二進制代碼。比如,我們在Windows平台上,可使用Visual C++開發環境,編寫程序並編譯成可執行程序。這種方式下,我們使用PC平台上的Windows工具開發針對Windows本身的可執行程序,這種編譯過程稱為native compilation,中文可理解為本機編譯。然而,在進行嵌入式系統的開發時,運行程序的目標平台通常具有有限的存儲空間和運算能力,比如常見的ARM 平台,其一般的靜態存儲空間大概是16到32MB,而CPU的主頻大概在100MHz到500MHz之間。這種情況下,在ARM平台上進行本機編譯就不太可能了,這是因為一般的編譯工具鏈(compilation tool chain)需要很大的存儲空間,並需要很強的CPU運算能力。為了解決這個問題,交叉編譯工具就應運而生了。通過交叉編譯工具,我們就可以在CPU能力很強、存儲控件足夠的主機平台上(比如PC上)編譯出針對其他平台的可執行程序。

  要進行交叉編譯,我們需要在主機平台上安裝對應的交叉編譯工具鏈(crosscompilation tool chain),然后用這個交叉編譯工具鏈編譯我們的源代碼,最終生成可在目標平台上運行的代碼。常見的交叉編譯例子如下:
  1、在Windows PC上,利用ADS(ARM 開發環境),使用armcc編譯器,則可編譯出針對ARM CPU的可執行代碼。
  2、在Linux PC上,利用arm-linux-gcc編譯器,可編譯出針對Linux ARM平台的可執行代碼。
  3、在Windows PC上,利用cygwin環境,運行arm-elf-gcc編譯器,可編譯出針對ARM CPU的可執行代碼。

為什么要使用交叉編譯
  有時是因為目的平台上不允許或不能夠安裝我們所需要的編譯器,而我們又需要這個編譯器的某些特征;有時是因為目的平台上的資源貧乏,無法運行我們所需要編譯器;有時又是因為目的平台還沒有建立,連操作系統都沒有,根本談不上運行什么編譯器。

簡述linux系統啟動過程

  可以答之前的答之前的分析uboot的過程的部分。

Linux設備中字符設備和塊設備有什么主要區別?分別舉例。

  字符設備:字符設備是個能夠像字節流(類似文件)一樣被訪問的設備,由字符設備驅動程序來實現這種特性。字符設備驅動程序通常至少實現open, close,read和 write系統謂用。字符終端、串口、鼠標、鍵盤、攝像頭、聲卡和顯卡等就是典型的字符設備。

  塊設備:和字符設備類似,塊設備也是通過/dev目錄下的文件系統節點來訪問。塊設備上能夠容納文件系統,如:u盤,SD卡,磁盤等。

  字符設備和塊設備的區別僅僅在於內核內部管理數據的方式,也就是內核及驅動程序之間的些不同對用戶來講是透明的。

主設備號和次設備號的用途
在這里插入圖片描述
  Linux各種設備都以文件的形式存放在/dev目錄下,稱為設備文件。

  應用程序可以打開、關閉和讀寫這些設備文件,完成對設備的操作,就像操作普通的數據文件一樣。為了管理這些設備,系統為設備編了號,每個設備號又分為主設備號和次設備號。主設備號用來區分不同種類的設備,而次設備號用來區分同一類型的多個設備。對於常用設備,Linux有約定俗成的編號,如硬盤的主設備號是3。

   一個字符設備或者塊設備都有一個主設備號和次設備號。主設備號和次設備號統稱為設備號。主設備號用來表示一個特定的驅動程序。次設備號用來表示使用該驅動程序的各設備。例如一個嵌入式系統,有兩個LED指示燈,LED燈需要獨立的打開或者關閉。那么,可以寫一個LED燈的字符設備驅動程序,可以將其主設備號注冊成5號設備,次設備號分別為1和2。這里,次設備號就分別表示兩個LED燈。

同步通信和異步通信

同步通信和異步通信
   串行通信可以分為兩種類型,一種叫同步通信,另一種叫異步通信。

   同步通信方式,是把許多字符組成一個信息組,這樣,字符可以一個接一個地傳輸,但是,在每組信息(通常稱為信息幀)的開始要加上同步字符,在沒有信息要傳輸時,要填上空字符,因為同步傳輸不允許有間隙。同步方式下,發送方除了發送數據,還要傳輸同步時鍾信號,信息傳輸的雙方用同一個時鍾信號確定傳輸過程中每1位的位置。見右圖5.2所示。
在這里插入圖片描述

在這里插入圖片描述

   在異步通信方式中,兩個數據字符之間的傳輸間隔是任意的,所以,每個數據字符的前后都要用一些數位來作為分隔位。

   從下圖中可以看到,按標准的異步通信數據格式(叫做異步通信幀格式),1個字符在傳輸時,除了傳輸實際數據字符信息外,還要傳輸幾個外加數位。具體說,在1個字符開始傳輸前,輸出線必須在邏輯上處於“1”狀態,這稱為標識態。傳輸一開始,輸出線由標識態變為“0”狀態,從而作為起始位。起始位后面為5~8個信息位,信息位由低往高排列,即先傳字符的低位,后傳字符的高位。信息位后面為校驗位,校驗位可以按奇校驗設置,也可以按偶校驗設置,或不設校驗位。最后是邏輯的“1”作為停止位,停止位可為1位、1.5位或者2位。如果傳輸完1個字符以后,立即傳輸下一個字符,那么,后一個字符的起始位便緊挨着前一個字符的停止位了,否則,輸出線又會進入標識態。在異步通信方式中,發送和接收的雙方必須約定相同的幀格式,否則會造成傳輸錯誤。在異步通信方式中,發送方只發送數據幀,不傳輸時鍾,發送和接收雙方必須約定相同的傳輸率。當然雙方實際工作速率不可能絕對相等,但是只要誤差不超過一定的限度,就不會造成傳輸出錯。圖5.3是異步通信時的標准數據格式。

   比較起來,在傳輸率相同時,同步通信方式下的信息有效率要比異步方式下的高,因為同步方式下的非數據信息比例比較小。

在這里插入圖片描述
傳輸率
  所謂傳輸率就是指每秒傳輸多少位,傳輸率也常叫波特率。在計算機中,每秒傳輸多少位和波特率的含義是完全一致的,但是,在最初的定義上,每秒傳輸多少位和波特率是不同的,前者是指每秒鍾傳輸的數位是多少,而波特率是指每秒鍾傳輸的離散信號的數目。所謂離散信號,就是指不均勻的、不連續的也不相關的信號。在計算機里,只允許高電平和低電平兩種離散信號,它們分別表示l和0,於是,造成了波特率與每秒傳輸數位這兩者的吻合。但在其他一些場合,就未必如此。比如,采用脈沖調制時,可以允許取4種相位,而每種相位代表2個數位,這種情況下,按每秒傳輸多少位(bps)計算的傳輸率便是波特率的兩倍。

  國際上規定了一個標准波特率系列,標准波特率也是最常用的波特率,標准波特率系列為110、300、600、1200、1800、2400、4800、9600、19200......。

  大多數接口的波特率可以通過編程來指定。

  作為例子,我們可以考慮這樣一個異步傳輸過程:設每個字符對應1個起始位、7個數據位、1個奇/偶校驗位和1個停止位,如果波特率為1200,那么,每秒鍾能傳輸的最大字符數為1200/10=120個。

  作為比較,我們再來看一個同步傳輸的例子。假如也用1200的波特率工作,每個字符為7位,用4個同步字符作為信息幀頭部,但不用奇/偶校驗,那么,傳輸100個字符所用的時間為7×(100+4)/1200=0.6067,這就是說,每秒鍾能傳輸的字符數可達到100/0.6067=165個。
異步通信的差錯類型
  異步通信過程中,可能發生通信錯,一般有3種錯誤:
  1、幀格式錯:在應該接收到停止位的時候,接收到邏輯的“0”,便產生幀格式錯誤。
  2、奇偶錯:接收到的奇偶校驗位錯。
  3、覆蓋錯:通信接口接收到數據並存放到數據輸入寄存器中,但是CPU沒有及時來取,后面新接收的數據覆蓋了前面收到的數據,叫做覆蓋錯。
  發生幀格式錯和奇偶錯的原因可能為下面幾種:
  ◆ 發送和接收雙方采用了不同的傳輸率,或雖然雙方約定了相同的傳輸率,但傳輸率不可能絕對相等。在通信的速率比較高的情況下,如果雙方的傳輸率誤差達到一定的程度,也會造成通信出錯;
  ◆ 通信雙方采用了不相同的幀格式;
  ◆ 干擾。

Uart和IIC和SPI的區別(提示:關於異步和同步,電子器件上的)?

  UART、SPI、IIC是經常用到的幾個數據傳輸標准,下面分別總結一下:

  UART(Universal Asynchronous Receive Transmitter):也就是我們經常所說的串口,基本都用於調試。

  主機和從機至少要接三根線,RX、TX和GND。TX用於發送數據,RX用於接受數據(收發不是一根線,所以是全雙工方式)。注意A和B通信A.TX要接B.RX,A.RX要接B.TX(A用TX發B當然要用RX來收了!)

  如果A是PC機,B是單片機,A和B之間還要接一塊電平轉換芯片,用於將TTL/CMOS(單片機電平)轉換為RS232(PC機電平)。因為TTL/CMOS電平范圍是0 ~ 1.8/2.5/3.3/5V(不同單片機范圍不同),高電壓表示1,低電壓表示0。而RS232邏輯電平范圍-12V~ 12V-5~ -12表示高電平,+5~+12V表示低電平(對!你沒有聽錯)。為什么這么設置?這就要追溯到調制解調器出生時代了,有興趣自己去查資料!

  數據協議:以PC機A給單片機B發數據為例(1為高電平,0為低電平):A.TX to B.RX。剛開始B.RX的端口保持1,當A.TX發來一個0作為起始位告訴B我要發數據了!然后就開始發數據,發多少呢?通常一次是5位、6位、7位、8位,這個雙方事先要用軟件設置好。PC機一般會用串口助手設置,單片機會在uart的驅動中設置。一小幀數據發送完了以后,A.TX給個高電平告訴B.RX我發完了一幀。如果還有數據,就再給個0然后重復上一步。如果雙方約定由校驗位,還要在發停止位1之前發送個校驗位,不過現在一般都不需要校驗位了,因為出錯的概率太小了,而且一般用於調試。

  一般在串口助手上還有個RTS/CTS流控選項,也叫握手,我從來沒用過。搬一段我能理解的介紹:RTS(請求發送),CTS(清除發送)。如果要用這兩個功能,那就至少要接5根線:RX+TX+GND+RTS+CTS。當A要發送數據時,置RTS有效(可能是置1),告訴B我要發送數據了。當B准備好接受數據后,置CTS有效,告訴A你可以發了。然后他們就實現了兩次握手!挺耽誤時間是不是?這個RTS還可以當電源使用,如果你不用它的握手功能,且電源電流在50mA以下時,就可以把它置為高電平可以當電源用喔~!

  SPI(Serial Peripheral Interface, 同步外設接口)是由摩托羅拉公司開發的全雙工同步串行總線,該總線大量用在與EEPROM、ADC、FRAM和顯示驅動器之類的慢速外設器件通信。

  SPI是一種串行同步通訊協議,由一個主設備和一個或多個從設備組成,主設備啟動一個與從設備的同步通訊,從而完成數據的交換。SPI 接口由SDI(串行數據輸入),SDO(串行數據輸出),SCK(串行移位時鍾),CS(從使能信號)四種信號構成,CS 決定了唯一的與主設備通信的從設備,片選信號低電平有效。如沒有CS 信號,則只能存在一個從設備,主設備通過產生移位時鍾來發起通訊。通訊時,數據由SDO 輸出,SDI 輸入,數據在時鍾的上升或下降沿由SDO 輸出,在緊接着的下降或上升沿由SDI 讀入,這樣經過8/16 次時鍾的改變,完成8/16 位數據的傳輸。(極性 空閑高低電平 相位 那個跳邊沿采樣)

  IIC(Inter Integrated Circuit):兩根線:一個時鍾線SCL和一個數據線SDA。只有一根數據線,所以是半雙工通信。接線不難,而且兩根線上也可以掛很多設備(每個設備的IIC地址不同),數據協議比較麻煩:

  還是假設A給B發數據(這里A.SCL接B.SCL, A.SDA接B.SDA)。起初SDA和SCL上的電平都為高電平。然后A先把SDA拉低,等SDA變為低電平后再把SCL拉低(以上兩個動作構成了iic的起始位),此時SDA就可以發送數據了,與此同時,SCL發送一定周期的脈沖(周期和PCLK有關,一般會在IIC的控制寄存器中設置)。SDA發送數據和SCL發送脈沖的要符合的關系是:SDA必須在SCL是高電平是保持有效,在SCL是低電平時發送下一位(SCL會在上升沿對SDA進行采樣)。規定一次必須傳8位數據,8位數據傳輸結束后A釋放SDA,但SCL再發一個脈沖(這是第九個脈沖),這會觸發B通過將SDA置為低電平表示確認(該低電平稱為ACK)。最后SCL先變為高電平,SDA再變為高電平(以上兩個動作稱為結束標志)如果B沒有將SDA置為0,則A停止發送下一幀數據。IIC總線(即SDA和SCL)上的每個設備都有唯一地址,數據包傳輸時先發送地址位,接着才是數據。一個地址字節由7個地址位(可以掛128個設備)和1個指示位組成(7位尋址模式)。指示位是0表示寫,1表示讀。還有10位尋址模式,使用兩個字節來保存地址,第一個字節的最低兩位和第二個字節的8位合起來構成10位地址。

  在I2C總線的應用中應注意的事項總結為以下幾點 :
  1) 嚴格按照時序圖的要求進行操作,
  2) 若與口線上帶內部上拉電阻的單片機接口連接,可以不外加上拉電阻。
  3) 程序中為配合相應的傳輸速率,在對口線操作的指令后可用NOP指令加一定的延時。
  4) 為了減少意外的干擾信號將EEPROM內的數據改寫可用外部寫保護引腳(如果有),或者在EEPROM內部沒有用的空間寫入標志字,每次上電時或復位時做一次檢測,判斷EEPROM是否被意外改寫。

  關於IIC總線的操作注意事項
  1、對IIC總線的一次操作完之后,需要等待一段時間才能進行第二次操作。否則是啟動不了總線的:)
  2、在時鍾線(SCL)為高電平的時候,一定不能動數據線(SDA)狀態,除非是啟動或者結束總線

UART, SPI, IIC的區別與聯系:

  第一個區別當然是名字:
  UART(Universal Asynchronous Receiver Transmitter:通用異步收發器)
  SPI(Serial Peripheral Interface:串行外設接口);
  I2C(INTER IC BUS)

  第二,區別在電氣信號線上:
  SPI總線由三條信號線組成:串行時鍾(SCLK)、串行數據輸出(SDO)、串行數據輸入(SDI)。SPI總線可以實現 多個SPI設備互相連接。提供SPI串行時鍾的SPI設備為SPI主機或主設備(Master),其他設備為SPI從機或從設備(Slave)。主從設備間可以實現全雙工通信當有多個從設備時,還可以增加一條從設備選擇線。

  如果用通用IO口模擬SPI總線,必須要有一個輸出口(SDO),一個輸入口(SDI),另一個口則視實現的設備類型而定,如果要實現主從設備,則需輸入輸出口,若只實現主設備,則需輸出口即可,若只實現從設備,則只需輸入口即可。

  I2C總線是雙向、兩線(SCL、SDA)、串行、多主控(multi-master)接口標准,具有總線仲裁機制,非常適合在器件之間進行近距離、非經常性的數據通信。在它的協議體系中,傳輸數據時都會帶上目的設備的設備地址,因此可以實現設備組網。
如果用通用IO口模擬I2C總線,並實現雙向傳輸,則需一個輸入輸出口(SDA),另外還需一個輸出口(SCL)。(注:I2C資料了解得比較少,這里的描述可能很不完備)

  UART總線是異步串口,因此一般比前兩種同步串口的結構要復雜很多,一般由波特率產生器(產生的波特率等於傳輸波特率的16倍)、UART接收器、UART發送器組成,硬件上由兩根線,一根用於發送,一根用於接收。

  顯然,如果用通用IO口模擬UART總線,則需一個輸入口,一個輸出口。

  第三,從第二點明顯可以看出,SPI和UART可以實現全雙工,但I2C不行;

  第四,看看牛人們的意見吧!

  wudanyu:I2C線更少,我覺得比UART、SPI更為強大,但是技術上也更加麻煩些,因為I2C需要有雙向IO的支持,而且使用上拉電阻,我覺得抗干擾能力較弱,一般用於同一板卡上芯片之間的通信,較少用於遠距離通信。SPI實現要簡單一些,UART需要固定的波特率,就是說兩位數據的間隔要相等,而SPI則無所謂,因為它是有時鍾的協議。

quickmouse:I2C的速度比SPI慢一點,協議比SPI復雜一點,但是連線也比標准的SPI要少。
  UART一幀可以傳5/6/7/8位,IIC必須是8位。IIC和SPI都從最高位開始傳。
  SPI用片選信號選擇從機,IIC用地址選擇從機。

  速率與傳輸距離
  SPI 速率與芯片有關,有的400K,有的到幾兆
  rs232速率一般最大115200
  iic一般應用400K,CAN最高可到1M;
  spi和iic一般應用在芯片之間通訊,RS232可應用與設備與設備之間短距離通訊,最大15米,CAN適用設備間通訊,抗干擾能力強,理論上通訊距離可到10KM

用串口發送十個字節就丟失一個兩個你會怎樣檢查;發送的時候對方設備不響應你該怎么辦

  1.發送方自己發自己收,看有無問題
  2.接收方自己發自己收,看有無問題
  3.都沒問題就是在發送過程中出現問題,
發送和接收的參數是否一致,比如波特率和奇偶校驗位
打印發送buffer的數據和接收buffer的數據
  4.檢查丟失的字節有什么規律
  5.邏輯分析儀查看發送和接收的時序,導出發送的數據和接收的數據
什么是邏輯分析儀?邏輯分析儀的參數、使用步驟和優勢

內核鏈表為什么具有通用性?

  內核中由於要管理大量的設備,但是各種設備各不相同,必須將他們統一起來管理,於是內核設計者就想到了使用通用鏈表來處理,通用鏈表看似神秘,實際上就是雙向循環鏈表,這個鏈表的每個節點都是只有指針域,沒有任何數據域。

  使用通用鏈表的好處是:1.通用鏈表中每個節點中沒有數據域,也就是說無論數據結構有多復雜在鏈表中只有前后級指針。2.如果一個數據結構(即是描述設備的設備結構體)想要用通用鏈表管理,只需要在結構體中包含節點的字段即可。3.雙向鏈表可以從任意一個節點的前后遍歷整個鏈表,遍歷非常方便。4.使用循環鏈表使得可以不斷地循環遍歷管理節點,像進程的調度:操作系統會把就緒的進程放在一個管理進程的就緒隊列的通用鏈表中管理起來,循環不斷地,為他們分配時間片,獲得cpu進行周而復始的進程調度。

通用(內核)鏈表詳解

分配內存哪些函數?kmalloc有兩個參數,各個作用是什么?

  見前Arm部分

有哪些鎖,各自的效率問題?自選鎖怎樣實現的?

  鎖是線程同步時的一個重要的工具,然而操作系統中包含了多種不同的鎖,各種鎖之間有什么不同呢?

信號量(Semaphore)

  信號量分為二元信號量和多元信號量,所謂二元信號量就是指該信號量只有兩個狀態,要么被占用,要么空閑;而多元信號量則允許同時被N個線程占有,超出N個外的占用請求將被阻塞。信號量是“系統級別”的,即同一個信號量可以被不同的進程訪問。
因為信號量可以使等待資源線程進入休眠狀態,因此適用於那些占用資源比較久的場合。如果共享資源的持有時間比較短,那就不適合使用信號量了,因為頻繁的休眠、切換線程引起的開銷要遠大於信號量帶來的那點優勢。

互斥量 (Mutex)

  和二元信號量類似(一元信號量就是互斥體), 唯一不同的是,互斥量的獲取和釋放必須是在同一個線程中進行的。如果一個線程去釋放一個並不是它所占有的互斥量是無效的。而信號量是可以由其它線程進行釋放的。

臨界區(Critical Section)

  術語中,把臨界區的鎖的獲取稱為進入臨界區,而把鎖的釋放稱為離開臨界區。臨界區是“進程級別”的,即它只在本進程的所有線程中可見,其它性質與互斥量相同(即誰獲取,誰釋放)

讀寫鎖(Read-Write Lock)

  適 用於一個特定的場合。比如對於一段線程間訪問的數據,如果程序大部分時間都是在讀取,而只有很少的時間才會寫入,那么使用前面幾種鎖時,每次讀取也是同樣 要申請鎖的,而這時其它的線程就無法再對此段數據進行讀取。可是,多個線程同時對一段數據進行讀取時,是不存在同步問題的,那么這些讀取時設置的鎖就影響了程序的性能。讀寫鎖的出現就是為了解決這個問題的。

  對於一個讀寫鎖,有兩種獲取方式:共享(Shared)或獨占 (Exclusive)。如果當前讀寫鎖處於空閑狀態,那么當多個線程同時以共享方式訪問該讀寫鎖時,都可以成功;而此時如果一個線程以獨占的方式訪問該 讀寫鎖,那么它會等待所有共享訪問都結束后才可以成功。在讀寫鎖被獨占訪問的過程中,再次共享和獨占請求訪問該鎖,都會進行等待狀態。

  當臨界區的一個文件可以被同時讀取,但是並不能被同時讀和寫。如果一個線程在讀,另一個線程在寫,那么很可能會讀取到錯誤的不完整的數據。讀寫自旋鎖是可以允許對臨界區的共享資源進行並發讀操作的。但是並不允許多個線程並發讀寫操作。如果想要並發讀寫,就要用到了順序鎖。
順序鎖
  順序鎖是讀寫鎖的優化版本,讀寫鎖不允許同時讀寫,而使用順序鎖可以完成同時進行讀和寫的操作,但並不允許同時的寫。雖然順序鎖可以同時進行讀寫操作,但並不建議這樣,讀取的過程並不能保證數據的完整性。

  順序鎖和讀寫鎖都是自旋鎖的一種,因為在等待自旋鎖的時候處於“自旋”狀態,因此鎖的持有時間不能太長,一定要短,否則的話會降低系統性能。如果臨界區比較大,運行時間比較長的話要選擇其他的並發處理方式,比如信號量和互斥體。

條件變量(Condition Variable)

  條件變量相當於一種通知機制。多個線程可以設置等待該條件變量,而一旦另外的線程設置了該條件變量(相當於喚醒條件變量)后,多個等待的線程就可以繼續執行了。

Linux內核硬中斷 / 軟中斷的原理和實現

一、概述

  從本質上來講,中斷是一種電信號,當設備有某種事件發生時,它就會產生中斷,通過總線把電信號發送給中斷控制器。

  如果中斷的線是激活的,中斷控制器就把電信號發送給處理器的某個特定引腳。處理器於是立即停止自己正在做的事,跳到中斷處理程序的入口點,進行中斷處理。
  (1)硬中斷
  由與系統相連的外設(比如網卡、硬盤)自動產生的。主要是用來通知操作系統系統外設狀態的變化。比如當網卡收到數據包的時候,就會發出一個中斷。我們通常所說的中斷指的是硬中斷(hardirq)。

  (2)軟中斷
  為了滿足實時系統的要求,中斷處理應該是越快越好。linux為了實現這個特點,當中斷發生的時候,硬中斷處理那些短時間就可以完成的工作,而將那些處理事件比較長的工作,放到中斷之后來完成,也就是軟中斷(softirq)來完成。

  (3)中斷嵌套

  Linux下硬中斷是可以嵌套的,但是沒有優先級的概念,也就是說任何一個新的中斷都可以打斷正在執行的中斷,但同種中斷除外。軟中斷不能嵌套,但相同類型的軟中斷可以在不同CPU上並行執行。

  (4)軟中斷指令
  int是軟中斷指令。
  中斷向量表是中斷號和中斷處理函數地址的對應表。
  int n -- 觸發軟中斷n。相應的中斷處理函數的地址為:中斷向量表地址 + 4 * n。

  (5)硬中斷和軟中斷的區別
  軟中斷是執行中斷指令產生的,而硬中斷是由外設引發的。
  硬中斷的中斷號是由中斷控制器提供的,軟中斷的中斷號由指令直接指出,無需使用中斷控制器。
  硬中斷是可屏蔽的,軟中斷不可屏蔽。
  硬中斷處理程序要確保它能快速地完成任務,這樣程序執行時才不會等待較長時間,稱為上半部。
  軟中斷處理硬中斷未完成的工作,是一種推后執行的機制,屬於下半部。
二、開關

  (1)硬中斷的開關

  簡單禁止和激活當前處理器上的本地中斷:

local_irq_disable();

local_irq_enable();

  保存本地中斷系統狀態下的禁止和激活:

unsigned long flags;

local_irq_save(flags);

local_irq_restore(flags);

  (2)軟中斷的開關
  禁止下半部,如softirq、tasklet和workqueue等:

local_bh_disable();

local_bh_enable();

  需要注意的是,禁止下半部時仍然可以被硬中斷搶占。

  (3)判斷中斷狀態

#define in_interrupt() (irq_count()) // 是否處於中斷狀態(硬中斷或軟中斷)

#define in_irq() (hardirq_count()) // 是否處於硬中斷

#define in_softirq() (softirq_count()) // 是否處於軟中斷

三、硬中斷

  (1)注冊中斷處理函數

  注冊中斷處理函數:

/**
 * irq: 要分配的中斷號
 * handler: 要注冊的中斷處理函數
 * flags: 標志(一般為0)
 * name: 設備名(dev->name)
 * dev: 設備(struct net_device *dev),作為中斷處理函數的參數
 * 成功返回0
 */
 
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, 
    const char *name, void *dev);
中斷處理函數本身:


typedef irqreturn_t (*irq_handler_t) (int, void *);
 
/**
 * enum irqreturn
 * @IRQ_NONE: interrupt was not from this device
 * @IRQ_HANDLED: interrupt was handled by this device
 * @IRQ_WAKE_THREAD: handler requests to wake the handler thread
 */
enum irqreturn {
    IRQ_NONE,
    IRQ_HANDLED,
    IRQ_WAKE_THREAD,
};
typedef enum irqreturn irqreturn_t;
#define IRQ_RETVAL(x) ((x) != IRQ_NONE)

  (2)注銷中斷處理函數

/**
 * free_irq - free an interrupt allocated with request_irq
 * @irq: Interrupt line to free
 * @dev_id: Device identity to free
 *
 * Remove an interrupt handler. The handler is removed and if the
 * interrupt line is no longer in use by any driver it is disabled.
 * On a shared IRQ the caller must ensure the interrupt is disabled
 * on the card it drives before calling this function. The function does
 * not return until any executing interrupts for this IRQ have completed.
 * This function must not be called from interrupt context.
 */
 
void free_irq(unsigned int irq, void *dev_id);

四、軟中斷

  (1)定義

  軟中斷是一組靜態定義的下半部接口,可以在所有處理器上同時執行,即使兩個類型相同也可以。

  但一個軟中斷不會搶占另一個軟中斷,唯一可以搶占軟中斷的是硬中斷。

  軟中斷由softirq_action結構體實現:

struct softirq_action {
    void (*action) (struct softirq_action *); /* 軟中斷的處理函數 */
};
目前已注冊的軟中斷有10種,定義為一個全局數組:

static struct softirq_action softirq_vec[NR_SOFTIRQS];
 
enum {
    HI_SOFTIRQ = 0, /* 優先級高的tasklets */
    TIMER_SOFTIRQ, /* 定時器的下半部 */
    NET_TX_SOFTIRQ, /* 發送網絡數據包 */
    NET_RX_SOFTIRQ, /* 接收網絡數據包 */
    BLOCK_SOFTIRQ, /* BLOCK裝置 */
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ, /* 正常優先級的tasklets */
    SCHED_SOFTIRQ, /* 調度程序 */
    HRTIMER_SOFTIRQ, /* 高分辨率定時器 */
    RCU_SOFTIRQ, /* RCU鎖定 */
    NR_SOFTIRQS /* 10 */
};

  (2)注冊軟中斷處理函數

/**
 * @nr: 軟中斷的索引號
 * @action: 軟中斷的處理函數
 */
 
void open_softirq(int nr, void (*action) (struct softirq_action *))
{
    softirq_vec[nr].action = action;
}
例如:

open_softirq(NET_TX_SOFTIRQ, net_tx_action);

open_softirq(NET_RX_SOFTIRQ, net_rx_action);

  (3)觸發軟中斷

&emsp;&emsp;調用raise_softirq()來觸發軟中斷。

void raise_softirq(unsigned int nr)
{
    unsigned long flags;
    local_irq_save(flags);
    raise_softirq_irqoff(nr);
    local_irq_restore(flags);
}
 
/* This function must run with irqs disabled */
inline void rasie_softirq_irqsoff(unsigned int nr)
{
    __raise_softirq_irqoff(nr);
 
    /* If we're in an interrupt or softirq, we're done
     * (this also catches softirq-disabled code). We will
     * actually run the softirq once we return from the irq
     * or softirq.
     * Otherwise we wake up ksoftirqd to make sure we
     * schedule the softirq soon.
     */
    if (! in_interrupt()) /* 如果不處於硬中斷或軟中斷 */
        wakeup_softirqd(void); /* 喚醒ksoftirqd/n進程 */
}
Percpu變量irq_cpustat_t中的__softirq_pending是等待處理的軟中斷的位圖,通過設置此變量即可告訴內核該執行哪些軟中斷。

static inline void __rasie_softirq_irqoff(unsigned int nr)
{
    trace_softirq_raise(nr);
    or_softirq_pending(1UL << nr);
}
 
typedef struct {
    unsigned int __softirq_pending;
    unsigned int __nmi_count; /* arch dependent */
} irq_cpustat_t;
 
irq_cpustat_t irq_stat[];
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#define or_softirq_pending(x) percpu_or(irq_stat.__softirq_pending, (x))
#define local_softirq_pending() percpu_read(irq_stat.__softirq_pending)

  喚醒ksoftirqd內核線程處理軟中斷。

static void wakeup_softirqd(void)
{
    /* Interrupts are disabled: no need to stop preemption */
    struct task_struct *tsk = __get_cpu_var(ksoftirqd);
 
    if (tsk && tsk->state != TASK_RUNNING)
        wake_up_process(tsk);
}

  在下列地方,待處理的軟中斷會被檢查和執行:

  a. 從一個硬件中斷代碼處返回時

  b. 在ksoftirqd內核線程中

  c. 在那些顯示檢查和執行待處理的軟中斷的代碼中,如網絡子系統中

  而不管是用什么方法喚起,軟中斷都要在do_softirq()中執行。如果有待處理的軟中斷,do_softirq()會循環遍歷每一個,調用它們的相應的處理程序。

  在中斷處理程序中觸發軟中斷是最常見的形式。中斷處理程序執行硬件設備的相關操作,然后觸發相應的軟中斷,最后退出。內核在執行完中斷處理程序以后,馬上就會調用do_softirq(),於是軟中斷開始執行中斷處理程序完成剩余的任務。

  下面來看下do_softirq()的具體實現。

asmlinkage void do_softirq(void)
{
    __u32 pending;
    unsigned long flags;
 
    /* 如果當前已處於硬中斷或軟中斷中,直接返回 */
    if (in_interrupt()) 
        return;
 
    local_irq_save(flags);
    pending = local_softirq_pending();
    if (pending) /* 如果有激活的軟中斷 */
        __do_softirq(); /* 處理函數 */
    local_irq_restore(flags);
}
/* We restart softirq processing MAX_SOFTIRQ_RESTART times,
 * and we fall back to softirqd after that.
 * This number has been established via experimentation.
 * The two things to balance is latency against fairness - we want
 * to handle softirqs as soon as possible, but they should not be
 * able to lock up the box.
 */
asmlinkage void __do_softirq(void)
{
    struct softirq_action *h;
    __u32 pending;
    /* 本函數能重復觸發執行的次數,防止占用過多的cpu時間 */
    int max_restart = MAX_SOFTIRQ_RESTART;
    int cpu;
 
    pending = local_softirq_pending(); /* 激活的軟中斷位圖 */
    account_system_vtime(current);
    /* 本地禁止當前的軟中斷 */
    __local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET);
    lockdep_softirq_enter(); /* current->softirq_context++ */
    cpu = smp_processor_id(); /* 當前cpu編號 */
 
restart:
    /* Reset the pending bitmask before enabling irqs */
    set_softirq_pending(0); /* 重置位圖 */
    local_irq_enable();
    h = softirq_vec;
    do {
        if (pending & 1) {
            unsigned int vec_nr = h - softirq_vec; /* 軟中斷索引 */
            int prev_count = preempt_count();
            kstat_incr_softirqs_this_cpu(vec_nr);
 
            trace_softirq_entry(vec_nr);
            h->action(h); /* 調用軟中斷的處理函數 */
            trace_softirq_exit(vec_nr);
 
            if (unlikely(prev_count != preempt_count())) {
                printk(KERN_ERR "huh, entered softirq %u %s %p" "with preempt_count %08x,"
                    "exited with %08x?\n", vec_nr, softirq_to_name[vec_nr], h->action, prev_count,
                    preempt_count());
            }
            rcu_bh_qs(cpu);
        }
        h++;
        pending >>= 1;
    } while(pending);
 
    local_irq_disable();
    pending = local_softirq_pending();
    if (pending & --max_restart) /* 重復觸發 */
        goto restart;
 
    /* 如果重復觸發了10次了,接下來喚醒ksoftirqd/n內核線程來處理 */
    if (pending)
        wakeup_softirqd(); 
 
    lockdep_softirq_exit();
    account_system_vtime(current);
    __local_bh_enable(SOFTIRQ_OFFSET);
}

  (4)ksoftirqd內核線程

  內核不會立即處理重新觸發的軟中斷。當大量軟中斷出現的時候,內核會喚醒一組內核線程來處理。這些線程的優先級最低(nice值為19),這能避免它們跟其它重要的任務搶奪資源。但它們最終肯定會被執行,所以這個折中的方案能夠保證在軟中斷很多時用戶程序不會因為得不到處理時間而處於飢餓狀態,同時也保證過量的軟中斷最終會得到處理。

  每個處理器都有一個這樣的線程,名字為ksoftirqd/n,n為處理器的編號。

static int run_ksoftirqd(void *__bind_cpu)
{
    set_current_state(TASK_INTERRUPTIBLE);
    current->flags |= PF_KSOFTIRQD; /* I am ksoftirqd */
 
    while(! kthread_should_stop()) {
        preempt_disable();
 
        if (! local_softirq_pending()) { /* 如果沒有要處理的軟中斷 */
            preempt_enable_no_resched();
            schedule();
            preempt_disable():
        }
 
        __set_current_state(TASK_RUNNING);
 
        while(local_softirq_pending()) {
            /* Preempt disable stops cpu going offline.
             * If already offline, we'll be on wrong CPU: don't process.
             */
             if (cpu_is_offline(long)__bind_cpu))/* 被要求釋放cpu */
                 goto wait_to_die;
 
            do_softirq(); /* 軟中斷的統一處理函數 */
 
            preempt_enable_no_resched();
            cond_resched();
            preempt_disable();
            rcu_note_context_switch((long)__bind_cpu);
        }
 
        preempt_enable();
        set_current_state(TASK_INTERRUPTIBLE);
    }
 
    __set_current_state(TASK_RUNNING);
    return 0;
 
wait_to_die:
    preempt_enable();
    /* Wait for kthread_stop */
    set_current_state(TASK_INTERRUPTIBLE);
    while(! kthread_should_stop()) {
        schedule();
        set_current_state(TASK_INTERRUPTIBLE);
    }
 
    __set_current_state(TASK_RUNNING);
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM