電動車夯,立凱-KY 8月營收創歷史新高

電池正極材料廠立凱-KY公布8月合併營收為1.47億元,年成長62.6%,月增23.1%,創下單月歷史新高;累計其前8月營收8.77億元、年增24.03%;公司指出,8月營收攀高,主因是受惠產品價格持續走揚、以及車電事業部新增加技術服務收入。

立凱-KY並表示,公司在中國大陸與五龍電動車集團的合作亦有重要斬獲。9月4日在中國杭州開幕的二十國集團(G20)首腦峰會,中國作為接待地主國為體現國家綠色和可持續發展的理念,首次使用純電動汽車作為G20峰會首長貴賓接待用車,即採用210輛五龍旗下長江汽車自主研發的電動中巴車、電動商務車作為會議VIP接待用車和會議核心區域工作用車;而長江電動汽車不僅造型新穎,其科技配置、智慧駕馭模式更是為各國來賓所讚譽。

立凱-KY已於上月底前完成與五龍的資本合作案,雙方預計將在資金及技術方面合作,以加速拓展大陸電動車市場版圖。

(本文內容由授權提供)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

聚甘新

推動電動車,中國要求加速住宅區充電設施建設

為確保住宅區居民的電動車充電需求,中國國家發改委、能源局、工信不予鑄件部聯合發出通知,要求各地區加速推廣住宅區的電動車充電基礎建設,並須推動既有之住宅區停車位的電氣化改造。

根據聯合通知,在既有停車位的改造方面,專用的固定車位需依照「一表一車位」之模式進行配套供電設施增加容量的改造,且每個停車位需配置適當的容量電能表。公用車位則需考量該區實際情形與電動車車主之充電需求,進行配套供電設施建設。

北京政府亦將針對住宅區停車位的電氣化建設提供專項資金等政策支持。

同時,聯合通知要求開始研究第三方充電服務企業、物業服務企業、車位產權方、業主委員會等多方共同參與住宅區充電基礎設施營運與市場化經營,並鼓勵導入集中改造、智慧充電管理、多用戶分時分享等營運模式,以提升營運水準。各地的主管部門亦可根據在地營運狀況推出合理的收費機制,以行程可永續經營的市場型態。

該通知同時要求推動住宅區充電設施的保險配套工作,保險涵蓋範圍包括:設施製造商、營運業務方、企業、用戶個人等。

中國將在京津冀魯、長江三角、珠江三角等三地的重點城市與各地能源發改委以及房地產主管部門合作推動示範性專案。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

聚甘新

[經驗棧]C#監測IPv4v6網速及流量

1、前言

  最近做項目需要用到監測網速及流量,我經過百度和牆內谷歌都沒能快速發現監測IPV6流量和網速的用例;也經過自己的一番查詢和調試,浪費了不少時間,現在作為經驗分享出來希望大家指正。

2、C#代碼

using System.Net.NetworkInformation;
using System.Timers;

namespace Monitor
{
    public class MonitorNetwork
    {      
        public string UpSpeed { get; set; }   
        public string DownSpeed { get; set; }
        public string AllTraffic { get; set; }            
        private string NetCardDescription { get; set; }    
        //建立連接時上傳的數據量
        private long BaseTraffic { get; set; }    
        private long OldUp { get; set; }    
        private long OldDown { get; set; }
        private NetworkInterface networkInterface { get; set; }
        private Timer timer = new Timer() { Interval = 1000 };
    
        public void Close()
        {
            timer.Stop();   
        }
    
        public MonitorNetwork(string netCardDescription)
        {   
            timer.Elapsed += Timer_Elapsed;    
            NetCardDescription = netCardDescription;    
            timer.Interval = 1000;     
        }

        public bool Start()
        {
            networkInterface = null;    
            NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();    
            foreach (var var in nics)
            {
                if (var.Description.Contains(NetCardDescription))
                {
                    networkInterface = var;
                    break;
                }
            }    
            if (networkInterface == null)
            {
                return false;
            }
            else
            {    
                BaseTraffic = (networkInterface.GetIPStatistics().BytesSent +
                               networkInterface.GetIPStatistics().BytesReceived);    
                OldUp = networkInterface.GetIPStatistics().BytesSent;    
                OldDown = networkInterface.GetIPStatistics().BytesReceived;   
                timer.Start();    
                return true;
            }
    
        }

        private string[] units = new string[] {"KB/s","MB/s","GB/s" };

        private void CalcUpSpeed()
        {
            long nowValue = networkInterface.GetIPStatistics().BytesSent;    
            int num = 0;
            double value = (nowValue - OldUp) / 1024.0;
            while (value > 1023)
            {
                value = (value / 1024.0);
                num++;
            }   
            UpSpeed = value.ToString("0.0") + units[num];    
            OldUp = nowValue;    
        }
    
        private void CalcDownSpeed()
        {
            long nowValue = networkInterface.GetIPStatistics().BytesReceived;   
            int num = 0;
            double value = (nowValue - OldDown) / 1024.0;     
            while (value > 1023)
            {
                value = (value / 1024.0);
                num++;
            }    
            DownSpeed = value.ToString("0.0") + units[num];    
            OldDown = nowValue;    
        }
    
        private string[] unitAlls = new string[] { "KB", "MB", "GB" ,"TB"};
    
        private void CalcAllTraffic()
        {
            long nowValue = OldDown+OldUp;    
            int num = 0;
            double value = (nowValue- BaseTraffic) / 1024.0;
            while (value > 1023)
            {
                value = (value / 1024.0);
                num++;
            }   
            AllTraffic = value.ToString("0.0") + unitAlls[num];
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            CalcUpSpeed();
            CalcDownSpeed();
            CalcAllTraffic();
        }
    }
}

3、胡說八道

  雖然沒能直接快速地百度到方法,但是實現這個需求的時候,心裏是有個譜,Windows系統能監測到這個網速和流量,沒理由實現不了,只需要一個方法將這個信息讀取出來就好。最後實現這個需求是利用了System.Net.NetworkInformation這個程序集,但是這個程序集沒有隻接提供網速監測的方法,而是提供了接收和發送數據量的屬性,需要自己計算出即使網速,所以這個網速不是特別的準確。

  這個程序集其實一開始就看到了,前輩方法中使用的是IPv4InterfaceStatistics類中的BytesReceived屬性和BytesSent屬性實現的,但是在這個程序集里沒有對應的IPv6類,恍恍惚惚。

  然後呢,我就下意識以為這個程序集比較老舊,不支持IPv6統計信息讀取,然後也是各種搜索無果,之後呢不死心想再來研究研究,東點點西瞅瞅,然後在NetworkInterface 類中發現了一個GetIPStatistics()方法,它的描述是“獲取此 NetworkInterface 實例的 IP 統計信息。”。

  然後就順理成章的事了,根據GetIPStatistics()返回的IPInterfaceStatistics實例中的BytesReceived屬性和BytesSent屬性就能獲取到收發的數據總量,然後根據這個信息就能計算出大約的網速。

  經測試,利用IPInterfaceStatistics實例是能讀取到IPv4和IPv6的總數據量的,因為這次的需求就是監測總量,如果需要單獨監測IPv6的可以用總量減去IPv4部分。

4、後記

​  老師以前喊我認真念書,我心想有百度還不夠嗎,再念能有百度聰明,有百度懂得多,後來漸漸明白,百度懂得多都是前輩的搬磚添瓦來的,共勉。

參考資料

  System.Net.NetworkInformation 命名空間

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

聚甘新

EOS基礎全家桶(十三)智能合約基礎

簡介

智能合約是現在區塊鏈的一大特色,而不同的鏈使用的智能合約的虛擬機各不相同,編碼語言也有很大差異。而今天我們開始學習EOS的智能合約,我也是從EOS初期一直開發合約至今,期間踩過無數坑,也在Stack Overflow上提過問(最後自己解決了),在實際生產中也積累了很多經驗,所以我會連續幾周分多次分享合約開發的經驗,今天先來點基礎的。

一些C++的編程基礎

EOS就是使用C++開發的,這也為它帶來了諸多好處,而合約也沿用C++作為開發語言,雖然合約中無法直接使用Boost等框架(你可以自己引入,但這也意味着合約會很大,會佔用大量賬號的內存),但是我們還是可以使用很多C++的小型庫,並伴隨着eosio.cdt的發展,融入了更多實用的合約功能。

如果你之前沒有使用C系列的開發語言做過開發,比如:C語言、C++或者是C#,那麼你需要先學習下C語言的基本語法和數據結構,這裏我不做展開,在我們的系列文章的開篇就介紹了我推薦的Learn EOS – c/c++ 教程英文版,有一定英語基礎的朋友可以直接看這個,其他朋友也可以在網上找一些C++的入門教程看下。

如果你已經有了一定的C語言基礎,那麼寫合約的話,你會發現需要的基礎也並不多,依葫蘆畫瓢就能寫出各種基礎功能了,所以,你並不需要擔心太多語言上的門檻,畢竟合約只是一個特定環境下運行的程序,你能用到的東西並不會很多。

CDT選擇

EOS的早期版本進行合約開發還沒有CDT工具,那時的合約藉助的是源碼中的工具eosiocpp,所以你看2018年的博客,進行合約編譯都是用它,但你現在是見不到了。隨着官方CDT的迭代,在CDT的1.4版本開始被官方推薦使用,CDT後面也經歷了幾個大的版本更新,逐步改善合約編寫方式,更加趨於簡潔、直觀。

但是不同的CDT版本,也意味着編譯器的不同,所以合約開發也會有所區別,比如一些語法變了,一些庫名稱變了,增加了一些新的標註……

我們的教程側重還是介紹最新的語法,所以推薦使用1.6以上的版本。我也會盡量在後面的介紹中補充說明老的CDT的寫法,方便大家對照網上其他老博客的合約。

來個HelloWorld

學習任何編程,我們都不能少了Mr.HelloWorld,先來給大家打個招呼吧。

#include <eosio/eosio.hpp>

using namespace eosio;

class [[eosio::contract]] hello : public contract
{
public:
    using contract::contract;

    [[eosio::action]] void hi(name user)
    {
        print("Hello, ", user);
    }
};

  

基本合約結構及類型

hello合約就是一個最簡單的合約了,而且還有一個可調用的action為hi。我們首先還是來介紹下一個合約的程序結構吧。

  • 程序頭

包含了引入的頭文件、庫文件等,還有全局的命名空間的引入等。

#include <eosio/eosio.hpp>

using namespace eosio;

  

這裏eosio庫是我們的合約基礎庫,所有和eos相關的類型和方法,都在這個庫裏面,而這個庫裏面eosio.hpp是基礎,包含了contract等的定義,所以所有的合約都要引入。

【CDT老版本】早期cdt版本中庫名稱不是eosio,而是eosiolib

默認的,我們引入了eosio的命名空間,因為eosio的所有內容都是在這個命名空間下的,所以我們全局引入,會方便我們後續的代碼編寫。

  • 合約類定義

其實就是定義了一個class,繼承contract,並通過[[eosio::contract]]標註這個類是一個合約。使用using引入contract也是為了後續代碼可以更簡潔。

class [[eosio::contract]] hello : public contract{
public:
    using contract::contract;
}

  

【CDT老版本】早期cdt版本中直接使用了CONTRACT來定義合約類,比如:CONTRACT hello: public contract {}

  • action定義

寫一個public的方法,參數盡量用簡單或者是eosio內置的類型定義,無返回值(合約調用無法返回任何結果,除非報錯),然後在用[[eosio::action]]標註這個方法是一個合約action就行。

注意:action的名稱要求符合name類型的規則,name規則請看下面的常用類型中的說明。

[[eosio::action]]
void hi( name user ) {
    print( "Hello, ", user);
}

  

因為合約無法調試,所以只能通過print來打印信息,或者直接通過斷言拋出異常來進行調試。

【CDT老版本】早期cdt版本中直接使用ACTION來定義方法,比如:ACTION hi( name user ){}

  • 常用類型
類型 說明 示例
name 名稱類型,賬號名、表名、action名都是該類型,只能使用26個小寫字母和1到5的数字,特殊可以使用小數點,總長不超過13。 name("hi") 或者 "hi"_n
asset 資產類型,Token都是使用該類型,包含了Token符號和小數位,是一個複合類型,字符形式為1.0000 EOS asset(10000, symbol("TADO", 4)就是1.0000 TADO)
uint64_t 無符號64位整型,主要數據類型,表主鍵、name實質都是改類型 uint64_t amount = 10000000;
  • 內置常用對象或方法

在合約中,contract基類提供了一些方便的內置對象。

首先是get_self()或者是_self,這個方法可以獲取到當前合約所在的賬號,比如你把hello合約部署到了helloworld111這個賬號,那麼get_self()就可以獲取到helloworld111。

然後是get_code()或者是_code,這個方法可以獲取到當前交易請求的action方法名,這個在進行內聯action調用時可以用於判斷入口action。

最後是get_datastream()或者_ds,這個方法獲取的是數據流,如果你使用的是複雜類型,或者是自定義類型,那麼你無法在方法的參數上直接獲取到反序列化的變量值,你必須自己通過數據流來解析。

常用的還有獲取當前時間current_time_point(),這個需要引入#include <eosio/transaction.hpp>

數據持久化

當然,合約裏面,我們總會有些功能需要把數據存下來,在鏈上持久化存儲。所以我們就需要定義合約表了。

合約的表存在相應的合約賬號中,可以劃分表範圍(scope),每個表都有一個主鍵,uint64_t類型的,還可以有多個其他索引,表的查詢都是基於索引的。

這裏先提一句,表數據所佔用的內存,默認是合約賬號的內存,也可以使用其他賬號的,但需要權限,這個以後我們再介紹。

我們擴展一下hello合約。

#include <eosio/eosio.hpp>
#include <eosio/transaction.hpp>

using namespace eosio;

class [[eosio::contract]] hello : public contract
{
public:
    using contract::contract;

    hello(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds), friend_table(get_self(), get_self().value)
    {
    }

    [[eosio::action]] void hi(name user)
    {
        print("Hello, ", user);

        uint32_t now = current_time_point().sec_since_epoch();

        auto friend_itr = friend_table.find(user.value);
        if (friend_itr == friend_table.end())
        {
            friend_table.emplace(get_self(), [&](auto &f) {
                f.friend_name = user;
                f.visit_time = now;
            });
        }
        else
        {
            friend_table.modify(friend_itr, get_self(), [&](auto &f) {
                f.visit_time = now;
            });
        }
    }

    [[eosio::action]] void nevermeet(name user)
    {
        print("Never see you again, ", user);

        auto friend_itr = friend_table.find(user.value);
        check(friend_itr != friend_table.end(), "I don't know who you are.");

        friend_table.erase(friend_itr);
    }

private:
    struct [[eosio::table]] my_friend
    {
        name friend_name;
        uint64_t visit_time;

        uint64_t primary_key() const { return friend_name.value; }
    };

    typedef eosio::multi_index<"friends"_n, my_friend> friends;

    friends friend_table;
};

  

可以看到,我們已經擴充了不少東西了,包括構造函數,表定義,多索引表配置,並完善了原先的hi方法,增加了nevermeet方法。

我們現在模擬的是這樣一個使用場景,我們遇到一個朋友的時候,就會和他打招呼(調用hi),如果這個朋友是一個新朋友,就會插入一條記錄到我們的朋友表中,如果是一個老朋友了,我們就會更新這個朋友的記錄中的訪問時間。當我們決定不再見這個朋友了,就是絕交了(調用nevermeet),我們就會把這個朋友的記錄刪除。

  • 表定義

首先我們需要聲明我們的朋友表。定義一個結構體,然後用[[eosio::table]]標註這個結構體是一個合約表。在結構體里定義一個函數名primary_key,返回uint64_t類型,作為主鍵的定義。

private:
    struct [[eosio::table]] my_friend
    {
        name friend_name;
        uint64_t visit_time;

        uint64_t primary_key() const { return friend_name.value; }
    };

  

我們這裏聲明了一個my_friend的表,合約的表名不在這裏定義,所以結構體的名稱不必滿足name的規則。我們定義了兩個字段,friend_name(朋友的名稱)和visit_time(拜訪時間),主鍵我們直接使用了friend_name,這個字段是name類型的,而name類型的實質就是一個uint64_t的類型(所以name的規則那麼苛刻)。

【CDT老版本】早期cdt版本中直接使用TABLE來定義合約表,比如:TABLE my_friend{}

  • 多索引表配置

合約里的表都是通過多索引來定義的,這是合約表的結構基礎。所以這裏才是定義表名和查詢索引的地方。

typedef eosio::multi_index<"friends"_n, my_friend> friends;

  

我們現在只介紹最簡單的單索引的定義,以後再介紹多索引的定義方式,這裏的"friends"_n就是定義表名,所以使用了name類型,之後my_friend是表的結構類型,typedef實質上就是聲明了一個類型別名,名字是friends的類型。

  • 構造函數

構造函數這裏並不是必須,但是為了我們能在全局直接使用合約表,所以我們要在構造函數進行表對象的實例化。

public:
    hello(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds), friend_table(get_self(), get_self().value)
    {
    }

private:
    friends friend_table;

  

這一段是標準合約構造函數,hello(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds),合約類型實例化時會傳入receiver也就是我們的合約賬號(一般情況下),code就是我們的action名稱,ds就是數據流。

friend_table(get_self(), get_self().value)這一段就是對我們定義的friend_table變量的實例化,friend_table變量就是我們定義的多索引表的friends類型的實例。在合約里我們就可以直接使用friend_table變量來進行表操作了。實例化時傳遞的兩個參數正是表所在合約的名稱和表範圍(scope),這裏都使用的是當前合約的名稱。

  • 查詢記錄

查詢有多種方式,也就是多索引表提供了多種查詢的方式,默認的,使用findget方法是直接使用主鍵進行查詢,下次我們會介紹使用第二、第三等索引來進行查詢。find返回的是指針,數據是否存在,需要通過判斷指針是否是指到了表末尾,如果等於表末尾,就說明數據不存在,否則,指針的值就是數據對象。get直接返回的就是數據對象,所以在調用get時,就必須傳遞數據不存在時的錯誤信息。

auto friend_itr = friend_table.find(user.value);
if (friend_itr == friend_table.end())
{
    //數據不存在
}else
{
    //數據存在
}

  

我們在hi方法中先查詢了user是否存在。如果不存在,我們就添加數據,如果存在了,就修改數據中的visit_time字段的值為當前時間。

  • 添加記錄

多索引的表對象添加記錄使用emplace方法,第一個參數就是內存使用的對象,第二個參數就是添加表對象時的委託方法。

uint32_t now = current_time_point().sec_since_epoch();

auto friend_itr = friend_table.find(user.value);
if (friend_itr == friend_table.end())
{
    friend_table.emplace(get_self(), [&](auto &f) {
        f.friend_name = user;
        f.visit_time = now;
    });
}
else
{
    //數據存在
}

  

這裏先定義了一個變量now來表示當前時間,正是使用的內置方法current_time_point(),這個還是用了它的sec_since_epoch()方法,是為了直接獲取秒單位的值。

我們查詢后發現這個user的數據不存在,所以就進行插入操作,內存直接使用的合約賬號的,所以使用get_self(),然後對錶數據對象進行賦值。

  • 修改記錄

多索引的表對象修改記錄使用modify方法,第一個參數是傳遞需要修改的數據指針,第二個參數是內存使用的對象,第二個參數就是表對象修改時的委託方法。

friend_table.modify(friend_itr, get_self(), [&](auto &f) {
    f.visit_time = now;
});

  

我們將查詢到的用戶對象的指針friend_itr傳入,然後內存還是使用合約賬號的,委託中,我們只修改visit_time的值(主鍵是不能修改的)。

  • 刪除記錄
  • 多索引的表對象刪除記錄使用erase方法,只有一個參數,就是要刪除的對象指針,有返回值,是刪除數據后的指針偏移,也就是下一條數據的指針。
auto friend_itr = friend_table.find(user.value);
check(friend_itr != friend_table.end(), "I don't know who you are.");

friend_table.erase(friend_itr);

  

我們的示例中,將查詢到的這條數據直接刪除,併為使用變量來接收下一條數據的指針,在連續刪除數據時,你會需要獲取下一條數據的指針,因為已刪除的數據的指針已經失效了。

編譯

編譯我們再之前也有過介紹,安裝了eosio.cdt后,我們就有了eosio-cpp命令,進入到合約文件夾中,直接執行以下命令就會在當前目錄生成wasm和abi文件。

eosio-cpp -abigen hello.cpp -o hello.wasm

注意:替換命令中使用的hello.cpp為實際合約代碼文件名,而hello.wasm為實際合約的wasm文件名。

當然,編譯不通過的時候,你就要看看錯誤是什麼了,這可能會考驗一下你的C++功底。

發布

決定了要發布的賬號后,記得要購買足夠的內存和抵押足夠的資源。合約的內存消耗我們可以大致這樣估算,看下編譯好了的合約wasm文件有多大,然後乘以10,就是你發布到鏈上大概所需的內存大小了。

發布合約我們使用cleos set contract命令,其後跟合約賬號名和合約目錄,為了方便,我建議你把合約的目錄名保持和合約文件名一致。

cleos set contract helloworld111 ./hello -p helloworld111

這裏我們給出的代碼是將hello目錄下的hello合約發布到helloworld111。我這裏的文件夾是hello,裏面的abi和wasm也都是hello,這樣你不用手動指定合約文件了。

總結

至此,我想大家應該對合約的編寫有了一個大致的了解了,至少你可以參照着寫個簡單的合約出來了,這其中還有很多技巧和高級用法,我會在後續的文章中繼續和大家分享。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

聚甘新

Java併發編程(05):悲觀鎖和樂觀鎖機制

本文源碼:GitHub·點這裏 || GitEE·點這裏

一、資源和加鎖

1、場景描述

多線程併發訪問同一個資源問題,假如線程A獲取變量之後修改變量值,線程C在此時也獲取變量值並且修改,兩個線程同時併發處理一個變量,就會導致併發問題。

這種并行處理數據庫的情況在實際的業務開發中很常見,兩個線程先後修改數據庫的值,導致數據有問題,該問題復現的概率不大,處理的時候需要對整個模塊體系有概念,才能容易定位問題。

2、演示案例

public class LockThread01 {
    public static void main(String[] args) {
        CountAdd countAdd = new CountAdd() ;
        AddThread01 addThread01 = new AddThread01(countAdd) ;
        addThread01.start();
        AddThread02 varThread02 = new AddThread02(countAdd) ;
        varThread02.start();
    }
}
class AddThread01 extends Thread {
    private CountAdd countAdd  ;
    public AddThread01 (CountAdd countAdd){
        this.countAdd = countAdd ;
    }
    @Override
    public void run() {
        countAdd.countAdd(30);
    }
}
class AddThread02 extends Thread {
    private CountAdd countAdd  ;
    public AddThread02 (CountAdd countAdd){
        this.countAdd = countAdd ;
    }
    @Override
    public void run() {
        countAdd.countAdd(10);
    }
}
class CountAdd {
    private Integer count = 0 ;
    public void countAdd (Integer num){
        try {
            if (num == 30){
                count = count + 50 ;
                Thread.sleep(3000);
            } else {
                count = count + num ;
            }
            System.out.println("num="+num+";count="+count);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

這裏案例演示多線程併發修改count值,導致和預期不一致的結果,這是多線程併發下最常見的問題,尤其是在併發更新數據時。

出現併發的情況時,就需要通過一定的方式或策略來控制在併發情況下數據讀寫的準確性,這被稱為併發控制,實現併發控制手段也很多,最常見的方式是資源加鎖,還有一種簡單的實現策略:修改數據前讀取數據,修改的時候加入限制條件,保證修改的內容在此期間沒有被修改。

二、鎖的概念簡介

1、鎖機制簡介

併發編程中一個最關鍵的問題,多線程併發處理同一個資源,防止資源使用的衝突一個關鍵解決方法,就是在資源上加鎖:多線程序列化訪問。鎖是用來控制多個線程訪問共享資源的方式,鎖機制能夠讓共享資源在任意給定時刻只有一個線程任務訪問,實現線程任務的同步互斥,這是最理想但性能最差的方式,共享讀鎖的機制允許多任務併發訪問資源。

2、悲觀鎖

悲觀鎖,總是假設每次每次被讀取的數據會被修改,所以要給讀取的數據加鎖,具有強烈的資源獨佔和排他特性,在整個數據處理過程中,將數據處於鎖定狀態,例如synchronized關鍵字的實現就是悲觀機制。

悲觀鎖的實現,往往依靠數據庫提供的鎖機制,只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據,悲觀鎖主要分為共享讀鎖和排他寫鎖。

排他鎖基本機制:又稱寫鎖,允許獲取排他鎖的事務更新數據,阻止其他事務取得相同的資源的共享讀鎖和排他鎖。若事務T對數據對象A加上寫鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的寫鎖。

3、樂觀鎖

樂觀鎖相對悲觀鎖而言,採用更加寬鬆的加鎖機制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務的開銷非常的占資源,樂觀鎖機制在一定程度上解決了這個問題。

樂觀鎖大多是基於數據版本記錄機制實現,為數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通過為數據庫表增加一個version字段來實現。讀取出數據時,將此版本號一同讀出,之後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號等於數據庫表當前版本號,則予以更新,否則認為是過期數據。樂觀鎖機制在高併發場景下,可能會導致大量更新失敗的操作。

樂觀鎖的實現是策略層面的實現:CAS(Compare-And-Swap)。當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能成功更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。

4、機制對比

悲觀鎖本身的實現機制就以損失性能為代價,多線程爭搶,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,加鎖的機制會產生額外的開銷,還有增加產生死鎖的概率,引發性能問題。

樂觀鎖雖然會基於對比檢測的手段判斷更新的數據是否有變化,但是不確定數據是否變化完成,例如線程1讀取的數據是A1,但是線程2操作A1的值變化為A2,然後再次變化為A1,這樣線程1的任務是沒有感知的。

悲觀鎖每一次數據修改都要上鎖,效率低,寫數據失敗的概率比較低,比較適合用在寫多讀少場景。

樂觀鎖並未真正加鎖,效率高,寫數據失敗的概率比較高,容易發生業務形異常,比較適合用在讀多寫少場景。

是選擇犧牲性能,還是追求效率,要根據業務場景判斷,這種選擇需要依賴經驗判斷,不過隨着技術迭代,數據庫的效率提升,集群模式的出現,性能和效率還是可以兩全的。

三、Lock基礎案例

1、Lock方法說明

lock:執行一次獲取鎖,獲取后立即返回;

lockInterruptibly:在獲取鎖的過程中可以中斷;

tryLock:嘗試非阻塞獲取鎖,可以設置超時時間,如果獲取成功返回true,有利於線程的狀態監控;

unlock:釋放鎖,清理線程狀態;

newCondition:獲取等待通知組件,和當前鎖綁定;

2、應用案例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockThread02 {
    public static void main(String[] args) {
        LockNum lockNum = new LockNum() ;
        LockThread lockThread1 = new LockThread(lockNum,"TH1");
        LockThread lockThread2 = new LockThread(lockNum,"TH2");
        LockThread lockThread3 = new LockThread(lockNum,"TH3");
        lockThread1.start();
        lockThread2.start();
        lockThread3.start();
    }
}
class LockNum {
    private Lock lock = new ReentrantLock() ;
    public void getNum (){
        lock.lock();
        try {
            for (int i = 0 ; i < 3 ; i++){
                System.out.println("ThreadName:"+Thread.currentThread().getName()+";i="+i);
            }
        } finally {
            lock.unlock();
        }
    }
}
class LockThread extends Thread {
    private LockNum lockNum ;
    public LockThread (LockNum lockNum,String name){
        this.lockNum = lockNum ;
        super.setName(name);
    }
    @Override
    public void run() {
        lockNum.getNum();
    }
}

這裏多線程基於Lock鎖機制,分別依次執行任務,這是Lock的基礎用法,各種API的詳解,下次再說。

3、與synchronized對比

基於synchronized實現的鎖機制,安全性很高,但是一旦線程失敗,直接拋出異常,沒有清理線程狀態的機會。顯式的使用Lock語法,可以在finally語句中最終釋放鎖,維護相對正常的線程狀態,在獲取鎖的過程中,可以嘗試獲取,或者嘗試獲取鎖一段時間。

四、源代碼地址

GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent

推薦閱讀:Java基礎系列

序號 文章標題
A01 Java基礎:基本數據類型,核心點整理
A02 Java基礎:特殊的String類,和相關擴展API
B01 Java併發:線程的創建方式,狀態周期管理
B02 Java併發:線程核心機制,基礎概念擴展
B03 Java併發:多線程併發訪問,同步控制
B04 Java併發:線程間通信,等待/通知機制

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

聚甘新

從一個計算器開始說起——C#中的工廠方法模式

工廠模式作為很常見的設計模式,在日常工作中出鏡率非常高,程序員們一定要掌握它的用法喲,今天跟着老胡一起來看看吧。

舉個例子

現在先讓我們來看一個例子吧,比如,要開發一個簡單的計算器,完成加減功能,通過命令行讀入形如1+1的公式,輸出2這個結果,讓我們看看怎麼實現吧。

 

第一個版本

這個版本裏面,我們不考慮使用模式,就按照最簡單的結構,怎麼方便怎麼來。

思路非常簡單,僅需要實現以下幾個方法

  • 取運算數
  • 取運算符
  • 輸出結果
     class Program
    {
        static int GetOperatorIndex(string input)
        {
            int operatorIndex = 0;
            for (; operatorIndex < input.Length; operatorIndex++)
            {
                if (!char.IsDigit(input[operatorIndex]))
                    break;
            }
            return operatorIndex;
        }

        static int GetOp(string input, int startIndex, int size = -1)
        {
            string subStr;
            if (size == -1)
            {
                subStr = input.Substring(startIndex);
            }
            else
            {
                subStr = input.Substring(startIndex, size);
            }
            return int.Parse(subStr);
        }

        static int CalculateExpression(string input)
        {
            var operatorIndex = GetOperatorIndex(input); //得到運算符索引
            var op1 = GetOp(input, 0, operatorIndex); //得到運算數1
            var op2 = GetOp(input, operatorIndex + 1); //得到運算數2
            switch (input[operatorIndex])
            {
                case '+':
                    return op1 + op2;
                case '-':
                    return op1 - op2;
                default:
                    throw new Exception("not support");
            }
        }

        static void Main(string[] args)
        {
            string input = Console.ReadLine();
            while(!string.IsNullOrEmpty(input))
            {
                var result = CalculateExpression(input);
                Console.WriteLine("={0}", result);
                input = Console.ReadLine();
            }            
        }
}

代碼非常簡單,毋庸置疑,這個運算器是可以正常工作的。這也可能是我們大部分人剛剛踏上工作崗位的時候可能會寫出的代碼。但它有着以下這些缺點:

  • 缺乏起碼的抽象,至少加和減應該能抽象出操作類。
  • 缺乏抽象造成了巨型客戶端,所有的邏輯都嵌套在了客戶端裏面。
  • 使用switch case缺乏擴展性,同時switch case也暗指了這部分代碼是屬於變化可能性比較高的地方,我們應該把它們封裝起來。而且不能把他們放在和客戶端代碼一起

接下來,我們引入我們的主題,工廠方法模式。
 

工廠方法模式版本

工廠方法模式使用一個虛擬的工廠來完成產品構建(在這裡是運算符的構建,因為運算符是我們這個程序中最具有變化的部分),通過把可變化的部分封裝在工廠類中以達到隔離變化的目的。我們看看UML圖:

依葫蘆畫瓢,我們設計思路如下:

  • 設計一個IOperator接口,對應抽象的Product
  • 設計AddOperator和SubtractOperator,對應具體Product
  • 設計IOperatorFactory接口生產Operator
  • 設計OperatorFactory實現抽象IFactory

關鍵代碼如下,其他讀取操作數之類的代碼就不在贅述。

  • IOperator接口
       interface IOperator
       {
           int Calculate(int op1, int p2);
       }
  • 具體Operator
	class AddOperator : IOperator
        {
            public int Calculate(int op1, int op2)
            {
                return op1 + op2;
            }
        }

        class SubtractOperator : IOperator
        {
            public int Calculate(int op1, int op2)
            {
                return op1 - op2;
            }
        }
  • Factory接口
	interface IOperatorFactory
        {
            IOperator CreateOperator(char c);
        }
  • 具體Factory
	class OperatorFactory : IOperatorFactory
        {
            public IOperator CreateOperator(char c)
            {
                switch(c)
                {
                    case '+':
                        return new AddOperator();
                    case '-':
                        return new SubtractOperator();
                    default:
                        throw new Exception("Not support");
                }
            }
        }
  • 在CalculateExpression裏面使用他們
   static IOperator GetOperator(string input, int operatorIndex)
       {
           IOperatorFactory f = new OperatorFactory();
           return f.CreateOperator(input[operatorIndex]);
       }

       static int CalculateExpression(string input)
       {
           var operatorIndex = GetOperatorIndex(input);
           var op1 = GetOp(input, 0, operatorIndex);
           var op2 = GetOp(input, operatorIndex + 1);
           IOperator op = GetOperator(input, operatorIndex);
           return op.Calculate(op1, op2);                    
       }

這樣,我們就用工廠方法重新寫了一次計算器,現在看看,好處有

  • 容易變化的創建部分被工廠封裝了起來,工廠和客戶端以接口的形式依賴,工廠內部邏輯可以隨時變化而不用擔心影響客戶端代碼
  • 工厂部分可以放在另外一個程序集,項目規劃會更加合理
  • 客戶端僅僅需要知道工廠和抽象的產品類,不需要再知道每一個具體的產品(不需要知道如何構建每一個具體運算符),符合迪米特法則
  • 擴展性增強,如果之後需要添加乘法multiple,那麼僅需要添加一個Operator類代表Multiple並且修改Facotry裏面的生成Operator邏輯就可以了,不會影響到客戶端
     

自此,我們已經在代碼裏面實現了工廠方法模式,但可能有朋友就會想,雖然現在擴展性增強了,但是新添加運算符還是需要修改已有的工廠,這不是違反了開閉原則么。。有沒有更好的辦法呢?當然是有的。
 

反射版本

想想工廠方法那個版本,我們為什麼增加新的運算符就會不可避免的修改現有工廠?原因就是我們通過switch case來硬編碼“教導”工廠如何根據用戶輸入產生正確的運算符,那麼如果有一種方法可以讓工廠自動學會發現新的運算符,那麼我們的目的不就達到了?

嗯,我想聰明的朋友們已經知道了,用屬性嘛,在C#中,這種方法完成類的自描述,是最好不過了的。

我們的設計思路如下:

  • 定義一個描述屬性以識別運算符
  • 在運算符中添加該描述屬性
  • 在工廠啟動的時候,掃描程序集以註冊所有運算符

代碼如下:

  • 描述屬性
    class OperatorDescriptionAttribute : Attribute
    {
        public char Symbol { get; }
        public OperatorDescriptionAttribute(char c)
        {
            Symbol = c;
        }
    }
  • 添加描述屬性到運算符
    [OperatorDescription('+')]
    class AddOperator : IOperator
    {
        public int Calculate(int op1, int op2)
        {
            return op1 + op2;
        }
    }

    [OperatorDescription('-')]
    class SubtractOperator : IOperator
    {
        public int Calculate(int op1, int op2)
        {
            return op1 - op2;
        }
    }
  • 讓工廠使用描述屬性
    class OperatorFactory : IOperatorFactory
    {
        private Dictionary<char, IOperator> dict = new Dictionary<char, IOperator>();
        public OperatorFactory()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            foreach (var type in assembly.GetTypes())
            {
                if (typeof(IOperator).IsAssignableFrom (type) 
                    && !type.IsInterface)
                {
                    var attribute = type.GetCustomAttribute<OperatorDescriptionAttribute>();
                    if(attribute != null)
                    {
                        dict[attribute.Symbol] = Activator.CreateInstance(type) as IOperator;
                    }
                }
            }
        }
        public IOperator CreateOperator(char c)
        {
            if(!dict.ContainsKey(c))
            {
                throw new Exception("Not support");
            }
            return dict[c];
        }
    }

經過這種改造,現在程序對擴展性支持已經很友好了,需要添加Multiple,只需要添加一個Multiple類就可以,其他代碼都不用修改,這樣就完美符合開閉原則了。

  [OperatorDescription('*')]
  class MultipleOperator : IOperator
  {
      public int Calculate(int op1, int op2)
      {
          return op1 * op2;
      }
  }

這就是我們怎麼一步步從最原始的代碼走過來,一點點重構讓代碼實現工廠方法模式,最終再完美支持開閉原則的過程,希望能幫助到大家。

其實關於最後那個通過標記屬性實現擴展,微軟有個MEF框架支持的很好,原理跟這個有點相似,有機會我們再聊聊MEF。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

聚甘新

JAVA設計模式 1【創建型】設計模式介紹、單例模式的理解與使用

數據結構我們已經學了一部分了。是該了解了解設計模式了。習慣了CRUD的你,也該了解了解這一門神器、我為啥要說是神器呢?

因為在大廠的面試環節、以及很多的比如

  • Springboot
  • Mybatis

等開源框架中、大量的使用到了設計模式。為了我們在之後學習源代碼的時候不再懵逼,為啥這代碼能這樣寫?為啥巴拉巴拉xxx

設計模式必須要肝完

簡介

設計模式,是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結
它是解決特定問題的一系列套路,是前輩們的代碼設計經驗的總結,具有一定的普遍性,可以反覆使用。其目的是為了提高代碼的可重用性、代碼的可讀性和代碼的可靠性。

總結下來說就是:一種設計經驗、一種設計套路

想一下,被前輩們總結下來的東西。使用這麼多年、凝結為精華的東西、該學

創建型

我們首先來了解一下什麼是創建型,創建型 作為設計模式的一種分類,是描述如何將一個對象創建出來的。

我們都知道,JAVA 作為一種面向對象編程,最關鍵的關鍵字new 用來實例化一個對象。創建型分類、則是描述:如何更好的創建出一個對象

單例模式理解

單例模式,從字面意思上了解就是:它只負責創建出一個對象。因此被稱為單例模式。理解一下:我們的計算機都會有一個任務管理器。而在一台windows 的電腦上。任何時候都只會實例化一個任務管理器對象。進而可以理解為單例模式

在一個任務管理器被調用的時候(對象已經被創建),再次使用ctrl+shift+esc 則任然返回這個已經被創建的任務管理器

UML 圖理解

可能我首次提到這個概念,所以簡介一下。

UML圖是用圖形化的方式表示軟件類與類之間的關係。用圖形化的方式,展示出眾多類之間的關聯關係。

類圖

如下圖,我用圖形的方式、描述了一個任務管理器類JobManagement.class
它有一個 私有化private的屬性management 類型為本身。
它有一個 公開的public 的方法getManagement() 返回類型為本身

  • 常用的類型修飾符就是 + 與 –

注意:“可見性”表示該屬性對類外的元素是否可見,包括公有(Public)、私有(Private)、受保護(Protected)和朋友(Friendly)4 種,在類圖中分別用符號+、-、#、~表示。
http://c.biancheng.net/view/1319.html

關聯關係

關聯關係就是用來表示:多個類之間存在怎麼樣的關係的表示方法。常用箭頭來表示。

虛線箭頭 依賴關係

虛線箭頭用來表示依賴關係 從使用類指向被依賴的類。這裏使用的類就是我們的main 方法。而被依賴類則是我們的任務管理器對象

菱形箭頭 聚合關係

聚合管理作為一種強關聯管理。一般用於成員變量的引用。

http://c.biancheng.net/view/1319.html

單例模式的特點

  • 對象只會被創建一次,並且重複使用
  • 全局提供一個訪問點。靜態訪問點
  • 構造方法私有

學以致用

public class JobManagement {

    private static volatile JobManagement management;

    private JobManagement() {

    }

    public static synchronized JobManagement getManagement() {

        if (null == management) {
            System.out.println("未創建任務管理器,正在創建。。");
            management = new JobManagement();
        } else {
            System.out.println("已經存在創建的任務管理器");
        }
        return management;
    }
}

任務管理器對象包含以及靜態類型的自身對象引用。以及將自身的構造方法進行私有化、使得外部無法直接創建對象。而需要這個對象,則需要調用get()方法進行獲取。

  • volatile 關鍵字將屬性在所有線程中同步

懶漢模式

	if (null == management) {
            System.out.println("未創建任務管理器,正在創建。。");
            management = new JobManagement();
        } 

懶漢模式則是在對象首次被訪問的時候才進行創建的。否則、若這個對象從未被引用、則對象是不會被創建的。而餓漢模式,剛剛相反。

餓漢模式

private static JobManagement management = new JobManagement();

餓漢模式則是則是在類被虛擬機加載的時候就創建一個示例出來。這樣在訪問之前就已經有對象被創建、線程也是安全的。

測試使用

    public static void main(String[] args) {

        JobManagement management1 = JobManagement.getManagement();
        System.out.println(management1);
        JobManagement management2 = JobManagement.getManagement();
        System.out.println(management2);

    }
----------------------------
未創建任務管理器,正在創建。。
JobManagement@1b6d3586
已經存在創建的任務管理器
JobManagement@1b6d3586

小結

單例模式在Java 的學習中還是有很多地方會使用到,對於我們學習的第一個,也是最簡單的模式,也是最常用的模式。記住它的特點:

  • 構造方法私有
  • 提供一個全局訪問點

參考

http://c.biancheng.net/view/1338.html

歡迎關注

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

聚甘新

xenomai內核解析之雙核系統調用(一)

版權聲明:本文為本文為博主原創文章,轉載請註明出處。如有錯誤,歡迎指正。博客地址:https://www.cnblogs.com/wsg1100/

目錄

  • xenomai 內核系統調用
    • 一、32位Linux系統調用
    • 二、32位實時系統調用
    • 三、 64位系統調用
    • 五、 實時系統調用表cobalt_syscalls
    • 六、實時系統調用權限控制cobalt_sysmodes
    • 參考

xenomai 內核系統調用

解析系統調用是了解內核架構最有力的一把鑰匙,在這之前先搞懂xenomai與linux兩個內核共存后系統調用是如何實現的。

為什麼需要系統調用

linux內核中設置了一組用於實現系統功能的子程序,稱為系統調用。系統調用和普通庫函數調用非常相似,只是系統調用由操作系統核心提供,運行於內核態,而普通的函數調用由函數庫或用戶自己提供,運行於用戶態

一般的,進程是不能訪問內核的。它不能訪問內核所佔內存空間也不能調用內核函數。CPU硬件決定了這些(這就是為什麼它被稱作“保護模式”

為了和用戶空間上運行的進程進行交互,內核提供了一組接口。透過該接口,應用程序可以訪問硬件設備和其他操作系統資源。這組接口在應用程序和內核之間扮演了使者的角色,應用程序發送各種請求,而內核負責滿足這些請求(或者讓應用程序暫時擱置)。實際上提供這組接口主要是為了保證系統穩定可靠,避免應用程序肆意妄行,惹出大麻煩。

系統調用在用戶空間進程和硬件設備之間添加了一个中間層。該層主要作用有三個:

  • 它為用戶空間提供了一種統一的硬件的抽象接口。比如當需要讀些文件的時候,應用程序就可以不去管磁盤類型和介質,甚至不用去管文件所在的文件系統到底是哪種類型。
  • 系統調用保證了系統的穩定和安全。作為硬件設備和應用程序之間的中間人,內核可以基於權限和其他一些規則對需要進行的訪問進行裁決。舉例來說,這樣可以避免應用程序不正確地使用硬件設備,竊取其他進程的資源,或做出其他什麼危害系統的事情。
  • 每個進程都運行在虛擬系統中,而在用戶空間和系統的其餘部分提供這樣一層公共接口,也是出於這種考慮。如果應用程序可以隨意訪問硬件而內核又對此一無所知的話,幾乎就沒法實現多任務和虛擬內存,當然也不可能實現良好的穩定性和安全性。在Linux中,系統調用是用戶空間訪問內核的惟一手段;除異常和中斷外,它們是內核惟一的合法入口。

Linux加上實時系統內核xenomai后,實時任務常調用xenomai系統調用來完成實時的服務,如果實時任務需要用到linux的服務,還會調用linux的系統調用。

一、32位Linux系統調用

linux應用程序除直接系統調用外還會由glibc觸發系統調用,glibc為了提高應用程序的性能,對一些系統調用進行了封裝。
32位系統系統調用使用軟中斷int 0x80指令實現,軟中斷屬於異常的一種,通過它陷入(trap)內核,trap在整理的文檔x86 Linux中斷系統有說明。tarp_init()中設置IDT(Interrupt Descriptor Table 每个中斷處理程序的地址都保存在一個特殊的位置)由關int 0x80的IDT如下:

static const __initconst struct idt_data def_idts[] = {
	......
	SYSG(IA32_SYSCALL_VECTOR,	entry_INT80_32),
	......
};

當生系統調用時,硬件根據向量號在 IDT 中找到對應的表項,即中斷描述符,進行特權級檢查,發現 DPL = CPL = 3 ,允許調用。然後硬件將切換到內核棧 (tss.ss0 : tss.esp0)。接着根據中斷描述符的 segment selector 在 GDT / LDT 中找到對應的段描述符,從段描述符拿到段的基址,加載到 cs 。將 offset 加載到 eip。最後硬件將 ss / sp / eflags / cs / ip / error code 依次壓到內核棧。於是開始執行entry_INT80_32函數,該函數在entry_32.S定義:

ENTRY(entry_INT80_32)
	ASM_CLAC
	pushl	%eax		/* pt_regs->orig_ax */
	SAVE_ALL pt_regs_ax=$-ENOSYS	/* *存儲當前用戶態寄存器,保存在pt_regs結構里*/
	/*
	 * User mode is traced as though IRQs are on, and the interrupt gate
	 * turned them off.
	 */
	TRACE_IRQS_OFF

	movl	%esp, %eax
	call	do_int80_syscall_32
.Lsyscall_32_done:
	.......
.Lirq_return:
	INTERRUPT_RETURN/*iret 指令將原來用戶態保存的現場恢復回來,包含代碼段、指令指針寄存器等。這時候用戶態
進程恢復執行。*/

在內核棧的最高地址端,存放的是結構 pt_regs,首先通過 push 和 SAVE_ALL 將當前用戶態的寄存器,保存在棧中 pt_regs 結構裏面.保存完畢后,關閉中斷,將當前棧指針保存到 eax,即do_int80_syscall_32的參數1。
調用do_int80_syscall_32=>do_syscall_32_irqs_on。先看看沒有ipipe時Linux實現如下:

__always_inline void do_syscall_32_irqs_on(struct pt_regs *regs)
{
	struct thread_info *ti = pt_regs_to_thread_info(regs);
	unsigned int nr = (unsigned int)regs->orig_ax;

	.....
	if (likely(nr < IA32_NR_syscalls)) {
		nr = array_index_nospec(nr, IA32_NR_syscalls);
		regs->ax = ia32_sys_call_table[nr](	/*根據系統調用號索引直接執行*/
			(unsigned int)regs->bx, (unsigned int)regs->cx,
			(unsigned int)regs->dx, (unsigned int)regs->si,
			(unsigned int)regs->di, (unsigned int)regs->bp);
	}
	syscall_return_slowpath(regs);
}

在這裏,將系統調用號從pt_reges中eax 裏面取出來,然後根據系統調用號,在系統調用表中找到相應的函數進行調用,並將寄存器中保存的參數取出來,作為函數參數。如果仔細比對,就能發現,這些參數所對應的寄存器,和 Linux 的註釋是一樣的。ia32_sys_call_table系統調用表生成後面解析(此圖來源於網絡)。

相關內核調用執行完后,一直返回到 do_syscall_32_irqs_on ,如果系統調用有返回值,會被保存到 regs->ax 中。接着返回 entry_INT80_32 繼續執行,最後執行 INTERRUPT_RETURN 。 INTERRUPT_RETURN 在 arch/x86/include/asm/irqflags.h 中定義為 iret ,iret 指令將原來用戶態保存的現場恢復回來,包含代碼段、指令指針寄存器等。這時候用戶態進程恢復執行。

系統調用執行完畢。

二、32位實時系統調用

Xenomai使用I-pipe 攔截常規Linux系統調用調度程序,並將系統調用定向到實現它們的系統。

實時系統調用,除了直接系統調用外,xenomai還實現了libcoblat實時庫,相當於glibc,通過libcoblat進行xenomai系統調用,以libcoblat庫函數sem_open為例,libcolat庫中C函數實現如下:

COBALT_IMPL(sem_t *, sem_open, (const char *name, int oflags, ...))
{
	......
	err = XENOMAI_SYSCALL5(sc_cobalt_sem_open,
			       &rsem, name, oflags, mode, value);
	if (err == 0) {
		if (rsem != sem)
			free(sem);
		return &rsem->native_sem;
	}
	.......
	return SEM_FAILED;
}

libcolat庫調用系統調用使用宏XENOMAI_SYSCALL5XENOAI_SYSCALL宏在\include\asm\xenomai\syscall.h中聲明,XENOMAI_SYSCALL5中的’5’代表’該系統調用有五個參數:

#define XENOMAI_DO_SYSCALL(nr, op, args...)			\
({								\
	unsigned __resultvar;					\
	asm volatile (						\
		LOADARGS_##nr					\
		"movl %1, %%eax\n\t"				\
		DOSYSCALL					\
		RESTOREARGS_##nr				\
		: "=a" (__resultvar)				\
		: "i" (__xn_syscode(op)) ASMFMT_##nr(args)	\
		: "memory", "cc");				\
	(int) __resultvar;					\
})

#define XENOMAI_SYSCALL0(op)			XENOMAI_DO_SYSCALL(0,op)
#define XENOMAI_SYSCALL1(op,a1)			XENOMAI_DO_SYSCALL(1,op,a1)
#define XENOMAI_SYSCALL2(op,a1,a2)		XENOMAI_DO_SYSCALL(2,op,a1,a2)
#define XENOMAI_SYSCALL3(op,a1,a2,a3)		XENOMAI_DO_SYSCALL(3,op,a1,a2,a3)
#define XENOMAI_SYSCALL4(op,a1,a2,a3,a4)	XENOMAI_DO_SYSCALL(4,op,a1,a2,a3,a4)
#define XENOMAI_SYSCALL5(op,a1,a2,a3,a4,a5)	XENOMAI_DO_SYSCALL(5,op,a1,a2,a3,a4,a5)

每個宏中,內嵌另一個宏DOSYSCALL,即實現系統調用的int指令:int $0x80

#define DOSYSCALL  "int $0x80\n\t"

系統調用過程硬件處理及中斷入口上節一致,從do_syscall_32_irqs_on開始不同,有ipipe后變成下面這樣子:

static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs)
{
	struct thread_info *ti = current_thread_info();
	unsigned int nr = (unsigned int)regs->orig_ax;/*取出系統調用號*/
	int ret;
	
	ret = pipeline_syscall(ti, nr, regs);/*pipeline 攔截系統調用*/
	......
done:
	syscall_return_slowpath(regs);
}

套路和ipipe接管中斷類似,在關鍵路徑上攔截系統調用,然後調用ipipe_handle_syscall(ti, nr, regs)讓ipipe來接管處理:

int ipipe_handle_syscall(struct thread_info *ti,
			 unsigned long nr, struct pt_regs *regs)
{
	unsigned long local_flags = READ_ONCE(ti->ipipe_flags);
	int ret; 
	if (nr >= NR_syscalls && (local_flags & _TIP_HEAD)) {/*運行在head域且者系統調用號超過linux*/
		ipipe_fastcall_hook(regs);			/*快速系統調用路徑*/
		local_flags = READ_ONCE(ti->ipipe_flags);
		if (local_flags & _TIP_HEAD) {
			if (local_flags &  _TIP_MAYDAY)
				__ipipe_call_mayday(regs);
			return 1; /* don't pass down, no tail work. */
		} else {
			sync_root_irqs();
			return -1; /* don't pass down, do tail work. */
		}
	}

	if ((local_flags & _TIP_NOTIFY) || nr >= NR_syscalls) {
		ret =__ipipe_notify_syscall(regs);
		local_flags = READ_ONCE(ti->ipipe_flags);
		if (local_flags & _TIP_HEAD)
			return 1; /* don't pass down, no tail work. */
		if (ret)
			return -1; /* don't pass down, do tail work. */
	}

	return 0; /* pass syscall down to the host. */
}

這個函數的處理邏輯是這樣,怎樣區分xenomai系統調用和linux系統調用?每個CPU架構不同linux系統調用總數不同,在x86系統中有300多個,用變量NR_syscalls表示,系統調用號與系統調用一一對應。首先獲取到的系統調用號nr >= NR_syscalls,不用多想,那這個系統調用是xenomai內核的系統調用。
另外還有個問題,如果是Linux非實時任務觸發的xenomai系統調用,或者xenomai 實時任務要調用linux的服務,這些交叉服務涉及實時任務與非實時任務在兩個內核之間運行,優先級怎麼處理等問題。這些涉及cobalt_sysmodes[].

首先看怎麼區分一個任務是realtime還是no_realtime。在task_struct結構的頭有一個成員結構體thread_info,存儲着當前線程的信息,ipipe在結構體thread_info中增加了兩個成員變量ipipe_flagsipipe_data,ipipe_flags用來來標示一個線程是實時還是非實時,_TIP_HEAD置位表示已經是實時上下文。對於需要切換到xenomai上下文的系統調用_TIP_NOTIFY置位。

struct thread_info {
	unsigned long		flags;		/* low level flags */
	u32			status;		/* thread synchronous flags */
#ifdef CONFIG_IPIPE
	unsigned long		ipipe_flags;
	struct ipipe_threadinfo ipipe_data;
#endif
};

ipipe_handle_syscall處理邏輯:
1.對於已經在實時上下文的實時任務發起xenomai的系統調用,使用快速調用路徑函數ipipe_fastcall_hook(regs);
2.需要切換到實時上下文或者非實時調用實時的,使用慢速調用路徑:

__ipipe_notify_syscall(regs)
->ipipe_syscall_hook(caller_domain, regs)

快速調用ipipe_fastcall_hook(regs)內直接handle_head_syscall執行代碼如下:

static int handle_head_syscall(struct ipipe_domain *ipd, struct pt_regs *regs)
{
	....
	code = __xn_syscall(regs);
	nr = code & (__NR_COBALT_SYSCALLS - 1);
	......
	handler = cobalt_syscalls[code];
	sysflags = cobalt_sysmodes[nr];
	........

	ret = handler(__xn_reg_arglist(regs));
	.......

	__xn_status_return(regs, ret);

	.......
}

這個函數很複雜,涉及xenomai與linux之間很多聯繫,代碼是簡化后的,先取出系統調用號,然後從cobalt_syscalls取出系統調用入口handler,然後執行handler(__xn_reg_arglist(regs))執行完成后將執行結果放到寄存器ax,後面的文章會詳細分析ipipe如何處理系統調用。

三、 64位系統調用

我們再來看 64 位的情況,系統調用,不是用中斷了,而是改用 syscall 指令。並且傳遞參數的寄存器也變了。

#define DO_SYSCALL(name, nr, args...)			\
({							\
	unsigned long __resultvar;			\
	LOAD_ARGS_##nr(args)				\
	LOAD_REGS_##nr					\
	asm volatile (					\
		"syscall\n\t"				\
		: "=a" (__resultvar)			\
		: "0" (name) ASM_ARGS_##nr		\
		: "memory", "cc", "r11", "cx");		\
	(int) __resultvar;				\
})

#define XENOMAI_DO_SYSCALL(nr, op, args...) \
	DO_SYSCALL(__xn_syscode(op), nr, args)

#define XENOMAI_SYSBIND(breq) \
	XENOMAI_DO_SYSCALL(1, sc_cobalt_bind, breq)

這裏將系統調用號使用__xn_syscode(op)處理了一下,把最高位置1,表示Cobalt系統調用,然後使用syscall 指令。

#define __COBALT_SYSCALL_BIT	0x10000000
#define __xn_syscode(__nr)	(__COBALT_SYSCALL_BIT | (__nr))

syscall 指令還使用了一種特殊的寄存器,我們叫特殊模塊寄存器(Model Specific Registers,簡稱 MSR)。這種寄存器是 CPU 為了完成某些特殊控制功能為目的的寄存器,其中就有系統調用。在系統初始化的時候,trap_init 除了初始化上面的中斷模式,這裏面還會調用 cpu_init->syscall_init。這裏面有這樣的代碼:

wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

rdmsr 和 wrmsr 是用來讀寫特殊模塊寄存器的。MSR_LSTAR 就是這樣一個特殊的寄存器, 當 syscall 指令調用的時候,會從這個寄存器裏面拿出函數地址來調用,也就是調entry_SYSCALL_64。
該函數在’entry_64.S’定義:

ENTRY(entry_SYSCALL_64)
	UNWIND_HINT_EMPTY
	......
	swapgs
	/*
	 * This path is only taken when PAGE_TABLE_ISOLATION is disabled so it
	 * is not required to switch CR3.
	 */
	movq	%rsp, PER_CPU_VAR(rsp_scratch)
	movq	PER_CPU_VAR(cpu_current_top_of_stack), %rsp

	/* Construct struct pt_regs on stack */
	pushq	$__USER_DS			/* pt_regs->ss */
	pushq	PER_CPU_VAR(rsp_scratch)	/* pt_regs->sp */
	pushq	%r11				/* pt_regs->flags */
	pushq	$__USER_CS			/* pt_regs->cs */
	pushq	%rcx				/* pt_regs->ip *//*保存用戶太指令指針寄存器*/
GLOBAL(entry_SYSCALL_64_after_hwframe)
	pushq	%rax				/* pt_regs->orig_ax */

	PUSH_AND_CLEAR_REGS rax=$-ENOSYS

	TRACE_IRQS_OFF

	/* IRQs are off. */
	movq	%rsp, %rdi
	call	do_syscall_64		/* returns with IRQs disabled */

	TRACE_IRQS_IRETQ		/* we're about to change IF */

	/*
	 * Try to use SYSRET instead of IRET if we're returning to
	 * a completely clean 64-bit userspace context.  If we're not,
	 * go to the slow exit path.
	 */
	movq	RCX(%rsp), %rcx
	movq	RIP(%rsp), %r11

	cmpq	%rcx, %r11	/* SYSRET requires RCX == RIP */
	jne	swapgs_restore_regs_and_return_to_usermode
	.......
	testq	$(X86_EFLAGS_RF|X86_EFLAGS_TF), %r11
	jnz	swapgs_restore_regs_and_return_to_usermode

	/* nothing to check for RSP */

	cmpq	$__USER_DS, SS(%rsp)		/* SS must match SYSRET */
	jne	swapgs_restore_regs_and_return_to_usermode

	/*
	 * We win! This label is here just for ease of understanding
	 * perf profiles. Nothing jumps here.
	 */
syscall_return_via_sysret:
	/* rcx and r11 are already restored (see code above) */
	UNWIND_HINT_EMPTY
	POP_REGS pop_rdi=0 skip_r11rcx=1

	/*
	 * Now all regs are restored except RSP and RDI.
	 * Save old stack pointer and switch to trampoline stack.
	 */
	movq	%rsp, %rdi
	movq	PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp

	pushq	RSP-RDI(%rdi)	/* RSP */
	pushq	(%rdi)		/* RDI */

	/*
	 * We are on the trampoline stack.  All regs except RDI are live.
	 * We can do future final exit work right here.
	 */
	SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi

	popq	%rdi
	popq	%rsp
	USERGS_SYSRET64
END(entry_SYSCALL_64)

這裏先保存了很多寄存器到 pt_regs 結構裏面,例如用戶態的代碼段、數據段、保存參數的寄存器.

然後調用 entry_SYSCALL64_slow_pat->do_syscall_64

__visible void do_syscall_64(struct pt_regs *regs)
{
	struct thread_info *ti = current_thread_info();
	unsigned long nr = regs->orig_ax;	/*取出系統調用號*/
	int ret;

	enter_from_user_mode();
	enable_local_irqs();

	ret = ipipe_handle_syscall(ti, nr & __SYSCALL_MASK, regs);
	if (ret > 0) {
		disable_local_irqs();
		return;
	}
	if (ret < 0)
		goto done;
	......
	if (likely((nr & __SYSCALL_MASK) < NR_syscalls)) {
		nr = array_index_nospec(nr & __SYSCALL_MASK, NR_syscalls);
		regs->ax = sys_call_table[nr](
			regs->di, regs->si, regs->dx,
			regs->r10, regs->r8, regs->r9);
	}
done:
	syscall_return_slowpath(regs);
}

與32位一樣,ipipe攔截了系統調用,後面的處理流程類似所以,無論是 32 位,還是 64 位,都會到linux系統調用表 sys_call_table和xenomai系統調用表cobalt_syscalls[] 這裏來。

五、 實時系統調用表cobalt_syscalls

xenomai每個系統的系統系統調用號在\cobalt\uapi\syscall.h中:


#define sc_cobalt_bind				0
#define sc_cobalt_thread_create			1
#define sc_cobalt_thread_getpid			2
	......
#define sc_cobalt_extend			96

bind()函數在內核代碼中對應的聲明和實現為:

/*聲明*/
#define COBALT_SYSCALL_DECL(__name, __args)	\
	long CoBaLt_ ## __name __args
static COBALT_SYSCALL_DECL(bind, lostage,
		      (struct cobalt_bindreq __user *u_breq));
/*實現*/
#define COBALT_SYSCALL(__name, __mode, __args)	\
	long CoBaLt_ ## __name __args
static COBALT_SYSCALL(bind, lostage,
		      (struct cobalt_bindreq __user *u_breq)){......}

其中__name表示系統調用名對應bind、__mode表示該系統調用模式對應lostage。COBALT_SYSCALL展開定義的bind函數后如下:

long CoBaLt_bind(struct cobalt_bindreq __user *u_breq){......}

怎麼將CoBaLt_bind與系統調用號sc_cobalt_bind聯繫起來後放入cobalt_syscalls[]的呢?
在編譯過程中Makefile使用腳本gen-syscall-entries.sh處理各個.c文件中的COBALT_SYSCALL宏,生成一個頭文件syscall_entries.h,裏面是對每個COBALT_SYSCALL宏處理后后的項,以上面COBALT_SYSCALL(bind,...)為例syscall_entries.h中會生成如下兩項,第一項為系統調用入口,第二項為系統調用的模式:

#define __COBALT_CALL_ENTRIES __COBALT_CALL_ENTRY(bind)
#define __COBALT_CALL_MODES	__COBALT_MODE(lostage)

實時系統調用表cobalt_syscalls[]定義在文件kernel\cobalt\posix\syscall.c中:

#define __syshand__(__name)	((cobalt_syshand)(CoBaLt_ ## __name))

#define __COBALT_NI	__syshand__(ni)

#define __COBALT_CALL_NI				\
	[0 ... __NR_COBALT_SYSCALLS-1] = __COBALT_NI,	\
	__COBALT_CALL32_INITHAND(__COBALT_NI)

#define __COBALT_CALL_NFLAGS				\
	[0 ... __NR_COBALT_SYSCALLS-1] = 0,		\
	__COBALT_CALL32_INITMODE(0)

#define __COBALT_CALL_ENTRY(__name)				\
	[sc_cobalt_ ## __name] = __syshand__(__name),		\
	__COBALT_CALL32_ENTRY(__name, __syshand__(__name))

#define __COBALT_MODE(__name, __mode)	\
	[sc_cobalt_ ## __name] = __xn_exec_##__mode,
	
#include "syscall_entries.h"		/*該頭文件由腳本生成*/

static const cobalt_syshand cobalt_syscalls[] = {
	__COBALT_CALL_NI
	__COBALT_CALL_ENTRIES
};

static const int cobalt_sysmodes[] = {
	__COBALT_CALL_NFLAGS
	__COBALT_CALL_MODES
};

__COBALT_CALL_NI宏表示數組空間大小為__NR_COBALT_SYSCALLS(128),每一項由__COBALT_CALL_ENTRIES定義,即腳本頭文件syscall_entries.h中生成的每一項來填充:

#define __COBALT_CALL_ENTRY(__name)				\
	[sc_cobalt_ ## __name] = __syshand__(__name),		\
	__COBALT_CALL32_ENTRY(__name, __syshand__(__name))

__COBALT_CALL32_ENTRY是定義兼容的系統調用,宏展開如下,相當於在數組的多個位置定義包含了同一項CoBaLt_bind

#define __COBALT_CALL32_ENTRY(__name, __handler)	\
	__COBALT_CALL32x_ENTRY(__name, __handler)	\
	__COBALT_CALL32emu_ENTRY(__name, __handler)

#define __COBALT_CALL32emu_ENTRY(__name, __handler)		\
			[sc_cobalt_ ## __name + 256] = __handler,
#define __COBALT_CALL32x_ENTRY(__name, __handler)		\
		[sc_cobalt_ ## __name + 128] = __handler,

最後bind系統調用在cobalt_syscalls[]中如下

static const cobalt_syshand cobalt_syscalls[] = {
	[sc_cobalt_bind] = CoBaLt_bind,
    [sc_cobalt_bind + 128] = CoBaLt_bind,   /*x32 support */
    [sc_cobalt_bind + 256] = CoBaLt_bind,   /*ia32 emulation support*/
	.....
};

相應的數組cobalt_sysmodes[]中的內容如下:

static const int cobalt_sysmodes[] = {
	[sc_cobalt_bind] = __xn_exec_bind,
    [sc_cobalt_bind + 256] = __xn_exec_lostage, /*x32 support */
    [sc_cobalt_bind + 128] = __xn_exec_lostage, /*ia32 emulation support*/
    ......
};

六、實時系統調用權限控制cobalt_sysmodes

上面說到,ipipe管理應用的系統調用時需要分清該系統調用是否合法,是否需要域切換等等。cobalt_sysmodes[]就是每個系統調用對應的模式,控制着每個系統調用的調用路徑。系統調用號為下標,值為具體模式。每個系統調用的sysmode如何生成見上一節,還是以實時應用的bind系統調用為例:

static const int cobalt_sysmodes[] = {
	[sc_cobalt_bind] = __xn_exec_bind,
    [sc_cobalt_bind + 256] = __xn_exec_lostage, /*x32 support */
    [sc_cobalt_bind + 128] = __xn_exec_lostage, /*ia32 emulation support*/
    ......
};

xenomai中所有的系統調用模式定義如下:

/*xenomai\posix\syscall.c*/
#define __xn_exec_lostage    0x1	/*必須在linux域運行該系統調用*/	
#define __xn_exec_histage    0x2	/*必須在Xenomai域運行該系統調用*/	
#define __xn_exec_shadow     0x4		/*影子系統調用:必須映射調用方*/
#define __xn_exec_switchback 0x8 	/*切換回切換; 調用者必須返回其原始模式*/
#define __xn_exec_current    0x10		/*在不管域直接執行。*/
#define __xn_exec_conforming 0x20  	/*在兼容域(Xenomai或Linux)中執行*/
#define __xn_exec_adaptive   0x40	/* 先直接執行如果返回-ENOSYS,則嘗試在相反的域中重新執行系統調用 */
#define __xn_exec_norestart  0x80  /*收到信號后不要重新啟動syscall*/
 /*Shorthand初始化系統調用的簡寫*/
#define __xn_exec_init       __xn_exec_lostage 
/*Xenomai空間中shadow系統調用的簡寫*/
#define __xn_exec_primary   (__xn_exec_shadow|__xn_exec_histage) 
/*Linux空間中shadow系統調用的簡寫*/
#define __xn_exec_secondary (__xn_exec_shadow|__xn_exec_lostage)
/*Linux空間中syscall的簡寫,如果有shadow則切換回linux*/
#define __xn_exec_downup    (__xn_exec_lostage|__xn_exec_switchback)
/* 主域系統不可重啟調用的簡寫 */
#define __xn_exec_nonrestartable (__xn_exec_primary|__xn_exec_norestart)
/*域探測系統調用簡寫*/
#define __xn_exec_probing   (__xn_exec_conforming|__xn_exec_adaptive)
/*將模式選擇移交給syscall。*/
#define __xn_exec_handover  (__xn_exec_current|__xn_exec_adaptive)

使用一個無符號32 位數的每一位來表示一種模式,各模式註釋已經很清楚,不在解釋,後面文章解析ipipe是如何根據mode來處理的。

參考

英特爾® 64 位和 IA-32 架構軟件開發人員手冊第 3 卷 :系統編程指南
極客時間專欄-趣談Linux操作系統
《linux內核源代碼情景分析》

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

聚甘新

維珍航空成功生產環保飛機燃料 減低環境影響

摘錄自2018年09月18日科技新報報導

維珍航空與 LanzaTech 從 2011 年開始研發環保飛機燃料技術,最近宣布終於開發成功,從鋼鐵煉製廠的工業廢氣提煉了 1,500 加侖飛機燃料。

此技術把原本排出大氣層的廢氣,透過發酵過程轉換成低碳乙醇 Lanzanol,首批 Lanzanol 在中國首鋼集團廠房生產。初步測試顯示,這種飛機燃料比傳統燃料減少 65% 碳排放,意味除了生產過程可持續,實際使用也相當環保。

兩家公司希望未來擴充生產,預計如果把計劃推展到全球鋼鐵煉製廠,可生產目前全球所有航班燃料的五分之一。維珍航空也希望明年可使用這種新燃料首次測試飛行。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

聚甘新

黑龍江大豆因霜害減產5.5億斤

摘錄自2018年9月18日大紀元報導

中國高度依賴進口大豆,中美貿易戰後,進口美國大豆數量驟減,黑龍江大豆主產區又遇9月上旬出現低溫霜凍天氣,導致近1,500萬畝大豆遭受凍害,估計減產5.5億斤。

中國農業農村部前(17)日通報,黑龍江省西北部地區9月9-10日出現低溫霜凍天氣,監測結果表明,大豆低溫霜凍面積約為1,478.5萬畝,產量損失預計為5.5億斤左右。大豆是喜溫作物,在整個生產期,大豆最適合生長的溫度是日平均氣溫20°~25°C,5-9月份氣溫平均降低1度,大豆畝產減產15-20斤。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

聚甘新