怎么運行c語言_C語言 原來是這樣調用硬件的

de8df760eeea76276cb637e8ec6f0794.png

大家都知道我們可以使用C語言寫一段程序來控制硬件工作,但你知道其工作原理嗎?

網友北極

C語言在實際運行中,都是以匯編指令的方式運行的,由編譯器把C語言編譯成匯編指令,CPU直接執行匯編指令。

所以這個問題就變成,匯編指令是如何操作硬件的?

如果把硬件平臺限制在x86環境下,那么匯編指令操作硬件基本上只有兩種方式:

方式一:

通過向內存空間寫數據。硬件會把硬件上的各種寄存器(外行可以理解為訪問硬件的接口或者操作硬件的工具)映射到某一塊內存地址空間上,之后只要用匯編指令,甚至C語言去讀寫這一段內存地址空間(并非真正操作物理內存),就可以達到操作硬件的目的了。

如果題主還有WindowsXP環境(虛擬機也可以),就可以用匯編指令直接操作顯存:

MOV AX,B800
MOV ES,AX
XOR DI,DI
MOV CX,0800
MOV AX,5555
REPZ STOSB

硬件的各種寄存器會被映射到某一塊物理內存中,這種方式稱為MMIO,在Windows的設備管理器里,右鍵點設備,看屬性-》資源里,不少硬件設備都有“內存范圍”的參數,這里的內存范圍就表示這個硬件的資源可以通過訪問這一段內存來控制它。

具體如下圖:

245314303393a7c10d379d367d317d76.png

方式二:

x86匯編中,還有兩個特殊的指令是IN和OUT,這是x86平臺獨有的,上面圖里的I/O范圍,就是用IN/OUT這兩個指令來訪問和控制的。

以上兩種訪問硬件的方式,第一種是可以用C語言實現的,上面一段匯編,本質上類似于C語言代碼:

char?ptr = 0xB8000;
int i;
for (i = 0; i 《0x800; i++)
{
ptr + i = 0x55;
}

第二種IN/OUT方式沒有直接的C語言語法對應,需要自己封裝匯編。

那么為什么平時很難用C語言操作硬件呢?這是因為平時寫的代碼大多數都在保護模式下,保護模式下,直接訪問物理地址會受到限制,C語言操作的地址都是虛地址。

對于Windows來說,要訪問物理地址,需要工作在內核模式,也就是的寫驅動才行。

而在顯存方面,首先,題主要先明白物理地址和虛擬地址的概念。

原來的8086cpu設計的時候,地址空間有一塊區域(640K-1M)之間,有一塊作為顯存使用

這里你說的預留的地址,是指物理地址,這一段地址的準確范圍是000A0000-000BFFFF,不管是32位還是64位CPU,這一段物理內存地址一直都保留給顯存使用,不區分32位還是64位,也不區分保護模式還是實模式。

以下是我電腦上的截圖(系統環境為Win7 64位,CPU是i7 4770K):

7a878e1d2963ba74257fd6a01503d770.png

可見這一段內存至今仍然是留給顯卡使用的。

那么現在為什么不能直接用這段內存了?

因為現在的軟件都運行在保護模式下,訪問的地址都是虛擬地址,而并非物理地址,包括你使用cmd命令打開的環境,都是虛擬地址,雖然32位XP里能用debug命令向000B8000上寫數據并能顯示在cmd的界面里,但本質上,這都是虛擬出來的。

如果要想用這段顯存怎么辦?

自己寫一個簡易的操作系統,不啟動顯卡的各種圖形加速功能,CPU進入保護模式后在GDT里映射一個4G的數據段,與物理地址一致,那么向000B8000上寫數據,就會像過去DOS一樣顯示在屏幕上,所以保護模式下也可以訪問這一段內存。所以,保護模式下,也可以用它。

顯卡那么多顯存是怎么映射的?

再看截圖:

efef32215282b92432b7a0e018aa2ebc.png

有很多內存地址被映射給顯存了,就是通過這種映射關系,把一些物理地址留給顯存,使得CPU能像訪問內存一樣訪問顯存資源。

當然,實際情況是,2G顯存未必完全映射,而是只映射一部分地址,顯卡有一些開放的寄存器能夠控制哪部分顯存映射過來,這樣就能使得CPU在使用比較少的物理地址范圍的情況下,訪問全部的顯存。

還有一個很有意思的事情:在虛擬機里,找到映射的高地址部分的第一塊內存區域,寫一個能直接訪問物理地址的程序(比如一個驅動),去讀這一塊內存,然后寫到文件里,再用屏幕截圖,也寫到文件里,會發現截圖的內容和顯存里讀出來的內容基本上是一樣的。

 網友awayisblue

要回答你的問題,我們需要要知道:

  1. 硬件是一種什么樣的存在

  2. 什么是驅動。

  3. C語言怎么操作硬件
    我就不嚴格去定義這些概念了,我就以一個例子來通俗地講解一下吧。
    首先講硬件:

  4. 先介紹一款單片機芯片STM8。

b504f89d8cf349b85df5af0afb68f2fe.png

  1. 這款芯片里面有cpu, 內存,寄存器(先不要覺得看到新名詞壓力大,繼續往下看)等等,相當于我們的電腦了,但還要外接其它硬件。

    這里你需要知道的概念是:

    芯片的引腳跟寄存器是相對應的,寄存器是8位的內存單元(對,存在于內存上面),當你往這個內存單元里面寫入數據時,芯片的引腳的電壓會發生變化,比如說我寫入的是01100001,則芯片上與之對應的8個引腳的電壓狀態(分為高電平與低電平兩種)會輸出:低高高低低低低高。

    cpu可以執行代碼指令,指令可以操作內存。

    結論:所以從上面兩點可以我們可以知道,cpu可以執行指令,使芯片的引腳電平(電壓)發生變化。

  2. 接下來我們再來看另一個硬件,液晶顯示器LCD1602(對,我就是這么迷你):

34c7b47b781928c5adc5fe2607a1a2b7.png

  1. 關于這款顯示器,我們需要知道的是:

    它是有引腳的,這些引腳可以跟到前面介紹的那款單片機芯片的引腳相連。
    該顯示器有自帶的內存,用于存儲要顯示的字符,顯示器從該內存里面讀取字符來來顯示。

    單片機芯片與該顯示器相連后,可以通過引腳往該顯示器的內存里寫數據(通過多個引腳電平的高低不同來代表不同的數據,比如說:低高高低低低低高 ?代表01100001,這個數據寫在顯示器的內存里面,被顯示器所顯示,當然,會根據ASCII來顯示數字對應的字符,01100001對應的字符是‘a’),除了接收數據的引腳外,還有控制顯示器的引腳(這個我們會在驅動那里介紹,繼續往下看)。

    結論:單片機芯片與顯示器相連,可以通過引腳輸出的電平來控制顯示器的字符顯示。

    那么,綜合上面,也就是說,單片機芯片cpu可以通過執行指令來控制顯示器的字符顯示。

    而這里,題主所說的硬件,指的就是這個顯示器了。

    接下來講驅動:

    那么,什么是驅動呢?驅動無非就是硬件跟軟件的中間層,但我們不糾結這種關系,直接來看一下,對于我們這個例子,驅動指的是什么。首先我們要知道:

    顯示器支持很多種操作,比如說清除顯示,光標移動,讀取數據,寫數據等等。

    這些操作數據引腳和控制引腳來實現。

    引腳可以通過單片機芯片來控制。

    結論:我們可以通過在單片機芯片里面寫顯示器的“驅動”程序來屏蔽掉硬件(顯示器硬件)層。

    于是這里驅動程序,指的是顯示器所支持操作的程序表示。比如說清除顯示,我們可以編寫一個clear()函數,光標移動,我們編寫一個move_cursor()函數,讀取數據和寫數據分別為read()和write(),然后分別實現就可以了(通過向寄存器里寫數據的形式,進而控制引腳的電平變化,再而控制顯示器,這個過程前面已有介紹)。這些函數就是驅動程序了。為什么上面說驅動程序可以屏蔽掉硬件呢?因為程序員可以使用前面的驅動程序來直接操作顯示器(硬件),而不用知道太多關于硬件的事情,而一般的驅動程序也可以由廠家來提供。

    再說明一點:一般這些驅動程序可以用匯編寫(出于運行效率的考慮),也可以用C語言來編寫的,比如說我上面的例子,就可以直接用C語言來編寫。當然C語言內聯匯編的形式也可以。

    最后講C語言怎么操作硬件:

    相信到這里,C語言是怎么操作硬件的已經比較明白了。
    這里總結一下:

    C語言由CPU運行(實際上是先編譯成機器碼存在芯片里面然后執行),可以去操作內存。

    內存里有一段是跟寄存器相對應的,而寄存器是跟芯片的引腳相對應的,于是操作該段內存就能控制芯片引腳的電壓變化。

    硬件(比如說顯示器)有引腳(或者說排線,這些也是一樣的東西),這些引腳跟芯片的引腳相連可以接受芯片的控制。

    可以把對某個硬件的操作做成一系列操作函數,這些操作函數就是驅動程序了。

    于是我們的C語言只要去調用這個驅動程序就可以直接操作硬件了。(當然驅動程序也可以由C語言來編寫,所以C語言操作硬件并不一定要經過驅動程序)。

網友Chow Anod

北極已經說的很到位了。我補充一些知識點:

1 ?語言層面上,C能直接操作的“硬件”只有內存地址。雖然C支持register關鍵字,但是不能指定某個特定的寄存器,所以只有內存地址。而C中操作內存地址的方式就是指針。例如:

char?p = 。..;p = 。..;

2 ?根據1反推,可以明白如果要開放給C來操作某個硬件,最直接的方案就是設計硬件的時候預先分配好一些固定的地址的用途,然后實際項目中往這些固定地址寫入合法的數據。這樣就可以通過類似
uint32_t?p = SCREEN_ADDR;p = RGBA(0xff,0xff,0xff,0xff);
這樣的代碼來實現對硬件的操作了。
3 ?那這個地址怎么拿到呢?什么樣的數據才是合法的呢?要解答這些問題,就需要查閱具體設備的spec了。例如這個一眼看過去就能的明白的例子(一眼沒看明白請反復閱讀以完全理解上面第二點內容):

PS:x86架構的代碼不能這么寫,原因見北極的回答。

網友北極

我們是用電腦的鍵盤來輸入的指令,每一個指令都對應一個ASCII碼,而這里的ASCII碼就是有序的電壓的高低(或電流的有無,下面只提電壓的高低),即我們輸入的是電壓的高低,你所看到代碼是這些電壓的高低控制顯示器所顯示的圖像,其實電腦也不知道它是什么,只知道這樣顯示。

結論:代碼其實就是存儲在存儲器(內存、硬盤或者閃存等等)中有序的電壓的高低。

再說編譯:

編譯是一個有序的電壓的高低向另一種有序的電壓高低的一種轉換過程,下面以52單片機為例,我們編譯是從表示ASCII碼的那種有序電壓高低轉換為52單片機能夠識別的另一種規定好的有序電壓高低,即表示HEX文件的電壓高低。

結論:編譯出的結果還是電腦中存儲的有序電壓高低。

到單片機燒錄:

接下倆就是燒錄,理解了上面兩點就很容易理解下面的內容,燒錄就是電腦中的有序電壓高低通過數據線傳輸到單片機中的ROM中。

接下來ROM就可以釋放其中的電壓來控制外圍的電路。

總結:從代碼的編輯到最后對電路的控制都是電壓在起作用,只是為了方面我們而給我們展現的形式不一樣而已,而其本質都是電壓,這樣也就不存在轉換。

理解這句話:世界上沒有軟件,軟件只是對硬件的一種反映,就像意識是對世界的一種反映是一樣的!

相信這樣就很容易理解了。

看到有人贊同了我的觀點,很開心,針對題目我再補充一點:

只要你提到0/1,提到軟件,這個問題就沒法理解。..因為軟件【包括0/1】和硬件始終存在一道無法跨越的鴻溝;

你說你在單片機中寫0,請問你是如何寫0的?在鍵盤上敲個0?實際還是電平【和我們理解的數字沒關系】,那個0只是你在電腦顯示器上電平的呈現形式,那個所謂的0【實質是電平】可以傳輸到單片機中的ROM中,電平控制電平沒什么疑問吧,這樣就輸出低電平了。

-END-

推薦閱讀

【01】C語言指針用得好猶如神助!這些使用技巧值得收藏【02】C語言中,全局變量濫用的后果竟如此嚴重?【03】太巧妙了!適合 MCU 用的 C語言 快速互轉 HEX (16進制) 方法!【04】值得收藏的 C語言 指針講解文章【05】嵌入式必會!C語言 最常用的貪心算法就這么被攻略了免責聲明:整理文章為傳播相關技術,版權歸原作者所有,如有侵權,請聯系刪除bec4943c9d4691f7caa3526dc288ab18.png

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

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

相關文章

四、元祖、字典

一、元祖(tuple):tu(11,"hello",(0,1),[11,"111"],33,) 元祖的特點:元祖中一級元素不可被修改,不能被增加或刪除,多級元素可以被修改,一般寫元祖的時候,推薦在后面添加逗號“&#xff…

PHP編程效率的20個要點

用單引號代替雙引號來包含字符串,這樣做會更快一些。因為PHP會在雙引號包圍的字符串中搜尋變量,單引號則 不會,注意:只有echo能這么做,它是一種可以把多個字符串當作參數的“函數”(譯注:PHP手冊中說echo是…

ubuntu運行python ide_打造vim中的python IDE

首先先介紹幾個常用的插件:1,ctags和taglist,這個大家估計都很常用,在ubuntu下只要安裝exuberant-ctags即可啦,另外tagbar支持面向對象語言的展示" toggle Tagbar displaymap :TagbarToggle" autofocus on T…

更新和插入的并發問題_mysql經典面試題:如何讀寫分離?主從原理是啥?同步的延時問題...

面試題你有沒有做 MySQL 讀寫分離?如何實現 MySQL 的讀寫分離?MySQL 主從復制原理的是啥?如何解決 MySQL 主從同步的延時問題?考點分析高并發這個階段,肯定是需要做讀寫分離的,啥意思?因為實際上…

php實現一個簡單的四則運算計算器

php實現一個簡單的四則運算計算器(還不支持括號的優先級)。利用棧這種數據結構來計算表達式很贊。 這里可以使用棧的結構,由于php的數組“天然”就有棧的特性,這里直接就利用了數組。當然可以使用棧結構寫,道理一樣的。…

Tcp與Ip協議的客戶端和服務器編程

Tcp與Ip協議的客戶端和服務器編程 本文就TCP和Ip協議的客戶端和服務器分別進行編程,實現了客戶端和服務端進行通信的功能,服務端對多個客戶端進行監聽,并能與多個客戶端通信。 服務器端代碼如下: using System; using System.Coll…

maven建立webapp項目時顯示Cannot change version of project facet Dynamic web module to 2.5

為什么80%的碼農都做不了架構師&#xff1f;>>> 網上查了很多東西都沒啥用&#xff0c;其實直接把這段代碼加到web.xml頭部&#xff0c;自然就不報錯了 <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns:xsi"http://www.…

python數據結構算法 北京大學_北京大學公開課《數據結構與算法Python版》

之前我分享過一個數據結構與算法的課程&#xff0c;很多小伙伴私信我問有沒有Python版。看了一些公開課后&#xff0c;今天特向大家推薦北京大學的這門課程&#xff1a;《數據結構與算法Python版》。課程概述很多同學想要轉行機器學習&#xff0c;也確實掌握了一些機器學習模型…

20道C#練習題(一)1——10題

1.輸入三個整數&#xff0c;xyz&#xff0c;最終以從小到大的方式輸出。利用if嵌套。 Console.Write("請輸入x"); double x double.Parse(Console.ReadLine()); Console.Write("請輸入y"); double y double.Parse(Console.ReadLine()); Console.Write(&q…

fd 句柄_linux文件描述符fd(windows下的句柄)

在Linux系統中一切皆可以看成是文件&#xff0c;文件又可分為&#xff1a;普通文件、目錄文件、鏈接文件和設備文件fd&#xff1a;file descriptor文件描述符0,1,2分別給了標準輸入、標準輸出和錯誤輸出。ls -l /proc/pid/fd可以查看某個進程所使用的fd用lsof可以查看比如&…

Python——三級菜單

#三級菜單函數 menu {北京&#xff1a;&#xff5b;海淀&#xff1a;&#xff5b;五道口&#xff1a;&#xff5b;&#xff5d;中關村&#xff1a;&#xff5b;&#xff5d;上帝&#xff1a;&#xff5b;&#xff5d;&#xff5d;昌平&#xff1a;&#xff5b;&#xff5d;朝陽…

HTTPS 原理解析

http://www.cnblogs.com/zery/p/5164795.html 一 前言 在說HTTPS之前先說說什么是HTTP&#xff0c;HTTP就是我們平時瀏覽網頁時候使用的一種協議。HTTP協議傳輸的數據都是未加密的&#xff0c;也就是明文的&#xff0c;因此使用HTTP協議傳輸隱私信息非常不安全。為了保證這些隱…

python 函數參數注解_python-如何使用函數注釋來驗證函數調用類...

我最近才發現有一種叫做函數注釋的東西,但是我不太確定如何使用它.這是我到目前為止的內容&#xff1a;def check_type(f):def decorated(*args, **kwargs):counter0for arg, type in zip(args, f.__annotations__.items()):if not isinstance(arg, type[1]):msg Not the vali…

SQL Server Replication 中關于視圖的點滴

在服務器A數據庫TEST新建了一個本地發布&#xff08;Local Publications&#xff09;RPL_GES_MIS_TEST,在服務器B數據庫RPL_TEST上創建了一個本地訂閱&#xff08;Local Subscriptions&#xff09;&#xff0c;它訂閱了了這個發布RPL_GES_MIS_TEST.如下截圖所示&#xff0c;本地…

kbmmw 5.0 中的REST 服務

目前關于REST 服務的話題越來越熱&#xff0c;kbmmw 在5.0 里面開始支持rest。今天我就試一下kbmmw 的 rest 服務。閑話少說&#xff0c;開始。 老規矩&#xff0c;放上兩個kbmMWServer1和 kbmMWHTTPSysServerTransport1兩個控件。 設置kbmMWHTTPSysServerTransport1的server 屬…

php7 匿名繼承類_PHP7匿名類的用法示例

本文實例講述了PHP7匿名類的用法。分享給大家供大家參考&#xff0c;具體如下&#xff1a;/*** Created by PhpStorm.* User: Itboot* Date: 2019/1/17* Time: 18:15*/class An{private $num;protected $age 15;public function __construct() {$this->num 1;}protected f…

python中不需要函數重載的原因

函數重載主要是為了解決兩個問題&#xff1a; 1.可變參數類型 2.可變參數個數 并且函數重載一個基本的設計原則是&#xff0c;僅僅當兩個函數除了參數類型和參數個數不同以外&#xff0c;其功能是完全相同的&#xff0c;此時才使用函數重載&#xff0c;如果兩個函數的功能其實不…

多租戶saas 架構_[譯/注] Force.com 多租戶互聯網應用開發平臺的設計

原文地址 http://cloud.pubs.dbs.uni-leipzig.de/sites/cloud.pubs.dbs.uni-leipzig.de/files/p889-weissman-1.pdf譯注&#xff1a;原文發表于 ACM&#xff0c;2009年6月作者Craig D Weissman, CTO, Salesforce.comSteve Bobrowski, Technical Marketing Consultant, Salesfor…

富文本

View Code轉載于:https://www.cnblogs.com/baidaye/p/5295448.html

Jenkins持續集成——用戶管理

一、基于安全矩陣系統管理 —>Configure Global Security相關權限的作用&#xff1a;其中有一些比較特別的權限&#xff1a;最大的權限是Overall的Administer&#xff0c;擁有該權限可以做任何事情。最基本的權限是Overall的Read&#xff0c;用戶必須賦予閱讀的權限&#xf…