標籤: 網頁設計公司

  • Spring Data 教程 – Redis

    Spring Data 教程 – Redis

    1. Redis簡介

    Redis(Remote Dictionary Server ),即遠程字典服務,是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value 數據庫,並提供多種語言的API。Redis 是一個高性能的key-value數據庫。 redis的出現,在部分場合可以對關係數據庫起到很好的補充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客戶端,使用很方便。

    Redis支持主從同步。數據可以從主服務器向任意數量的從服務器上同步,從服務器可以是關聯其他從服務器的主服務器。這使得Redis可執行單層樹複製。存盤可以有意無意的對數據進行寫操作。由於完全實現了發布/訂閱機制,使得從數據庫在任何地方同步樹時,可訂閱一個頻道並接收主服務器完整的消息發布記錄。同步對讀取操作的可擴展性和數據冗餘很有幫助。

    redis的key都是字符串String類型,它的value是多樣化的,如下圖:

    redis數據類型 ENCODING返回的編碼 底層對應的數據結構
    string int long類型的整數
    string embstr embstr編碼的簡單動態字符串
    string raw 簡單動態字符串
    list ziplist 壓縮列表
    list linkedlist 雙向鏈表
    hash ziplist 壓縮列表
    hash ht 字典
    set intset 整數集合
    set ht 字典
    zset ziplist 壓縮列表
    zset skiplist 跳錶

    2. Redis的五種數據類型

    2.1 字符串對象(String)

    字符串對象的模型:

    redis底層提供了三種不同的數據結構實現字符串對象,根據不同的數據自動選擇合適的數據結構。這裏的字符串對象並不是指的純粹的字符串,数字也是可以的。

    • int:當數據是long類型的整数字符串時,底層使用long類型的整數實現。這個值會直接存儲在字符串對象的ptr屬性中,同時OBJECT ENCODING為int。

    • raw:當數據為長度大於44字節的字符串時,底層使用簡單動態字符串實現,說到這裏就不得不提下redis的簡單隨機字符串(Simple Dynamic String,SDS),SDS有三個屬性,free,len和buf。free存的是還剩多少空間,len存的是目前字符串長度,不包含結尾的空字符。buf是一個list,存放真實字符串數據,包含free和空字符。針對SDS本文不做詳細介紹,歡迎點擊SDS了解。

    • embstr:當數據為長度小於44字節的字符串時,底層使用embstr編碼的簡單動態字符串實現。相比於raw,embstr內存分配只需要一次就可完成,分配的是一塊連續的內存空間。

    2.2 列表對象(List)

    列表對象的模型:

    redis中的列表對象經常被用作消息隊列使用,底層採用ziplist和linkedlist實現。大家使用的時候當作鏈表使用就可以了。

    • ziplist

      列表對象使用ziplist編碼需要滿足兩個要求,一是所有字符串長度都小於設定值值64字節(可以在配置文件中修改list-max-ziplist-value字段改變)。二是所存元素數量小於設定值512個(可以在配置文件中修改list-max-ziplist-entries字段改變)。ziplist類似與python中的list,佔用一段連續的內存地址,由此減小指針內存佔用。

      zlbytes:占內存總數

      zltail:到尾部的偏移量

      zllen:內部節點數

      node:節點

      zlend:尾部標識

      previous_entry_length:前一節點的長度

      encoding:數據類型

      content:真實數據

      遍歷的時候會根據zlbytes和zltail直接找到尾部節點nodeN,然後根據每個節點的previous_entry_length反向遍歷。增加和刪除節點會導致其他節點連鎖更新,因為每個節點都存儲了前一節點的長度。

    • linkedlist

      linkedlist有三個屬性,head,tail和len。head指向鏈表的頭部,tail指向鏈表的尾部,len為鏈表的長度。

    2.3 哈希類型對象(Hash)

    哈希類型對象的模型:

    redis的value類型hash類型,其實就是map類型,就是在值的位置放一個map類型的數據。大家想詳細了解一下,可以參考一下這篇文章:https://www.jianshu.com/p/658365f0abfc 。

    2.4 集合對象(Set)

    集合對象類型的模型:

    Set類型的value保證每個值都不重複。

    redis中的集合對象底層有兩種實現方式,分別有整數集合和hashtable。當所有元素都是整數且元素數小於512(可在配置文件中set-max-intset-entries字段配置)時採用整數集合實現,其餘情況都採用hashtable實現。hashtable請移駕上文鏈接查閱,接下來介紹整數集合intset。intset有三個屬性,encoding:記錄数字的類型,有int16,int32和int64等,length:記錄集合的長度,content:存儲具體數據。具體結構如下圖:

    2.5 有序集合對象

    有序集合對象(zset)和集合對象(set)沒有很大區別,僅僅是多了一個分數(score)用來排序。

    redis中的有序集合底層採用ziplist和skiplist跳錶實現,當所有字符串長度都小於設定值值64字節(可以在配置文件中修改list-max-ziplist-value字段改變),並且所存元素數量小於設定值512個(可以在配置文件中修改list-max-ziplist-entries字段改變)使用ziplist實現,其他情況均使用skiplist實現,跳躍表的實現原理這裏偷個懶,給大家推薦一篇寫的非常好的博客,點擊查看跳躍表原理。

    3. Redis的安裝

    可以去官網或者中文網下載Redis。redis的windows版本現在已經不更新了,所以我們安裝redis的6.0.3版本,這個版本支持的東西很多,在此次教程中,我們只對redis的五種數據類型做解釋和學習。

    官網:https://redis.io/

    中文網:https://www.redis.net.cn/

    本教程安裝的redis版本為6.0.3版本,redis使用C語言編寫的,CentOS7的gcc自帶版本為4.8.5,而redis6.0+需要的gcc版本為5.3及以上,所以需要升級gcc版本。

    下載Linux版本的tar.gz包,解壓以後進入解壓產生的包:

    cd redis-6.0.3
    

    發現沒有bin目錄,這裏需要通過make進行安裝。

    # 先檢查gcc的環境 
    gcc -v 
    # 查看gcc版本 
    yum -y install centos-release-scl 
    # 升級到9.1版本 
    yum -y install devtoolset-9-gcc devtoolset-9-gcc- c++ devtoolset-9-binutils 
    
    scl enable devtoolset-9 bash 
    #以上為臨時啟用,如果要長期使用gcc 9.1的話: 
    echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile 
    # 進入redis解壓文件 
    make 
    # 6.0的坑,gcc版本 9.0 以上
    # 等待完畢
    

    執行完make操作之後,就可以在redis目錄看到src目錄了。進入src目錄后就可以看到redis-serverredis-cli

    這裏建議將Redis的配置文件複製,保留一份原生的配置文件。

    redis的配置大家可以在網上搜一下常用的配置,在這裏給大家推薦一個常用的配置,比較詳細:

    https://blog.csdn.net/ymrfzr/article/details/51362125

    到這裏redis就可以啟動並且正常訪問了。

    注意:一定要將redis的IP地址綁定註釋掉,允許所有的IP地址訪問,不然我們從Windows訪問就訪問不了。

    註釋掉下面的這一行:

    同時關閉Redis的服務保護模式,將protected-mode設置為no。如下:

    4. Spring Boot 整合 Redis

    • 4.1 搭建工程,引入依賴

      搭建工程的操作我這裏就不在寫出來了。直接上pom.xml

      <!--springboot父工程-->
      <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>2.2.2.RELEASE</version>
          <relativePath/> <!-- lookup parent from repository -->
      </parent>
      
      <dependencies>
          <!--springboot-web組件-->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-web</artifactId>
              <version>2.2.2.RELEASE</version>
          </dependency>
          <!--redis整合springboot組件-->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-redis</artifactId>
              <version>2.3.0.RELEASE</version>
          </dependency>
          <!--lombok組件-->
          <dependency>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
              <version>1.18.10</version>
          </dependency>
      </dependencies>
      
    • 4.2 redis的配置

      項目的配置文件,application.yml

      butterflytri:
        host: 127.0.0.1
      server:
        port: 8080 # 應用端口
        servlet:
          context-path: /butterflytri # 應用映射
      spring:
        application:
          name: redis # 應用名稱
        redis:
          host: ${butterflytri.host} # redis地址
          port: 6379 # redis端口,默認是6379
          timeout: 10000 # 連接超時時間(ms)
          database: 0 # redis默認情況下有16個分片,這裏配置具體使用的分片,默認是0
          jedis: # 使用連接redis的工具-jedis
            pool:
              max-active: 8 # 連接池最大連接數(使用負值表示沒有限制) 默認 8
              max-wait: -1 # 連接池最大阻塞等待時間(使用負值表示沒有限制) 默認 -1
              max-idle: 8 # 連接池中的最大空閑連接 默認 8
              min-idle: 0 # 連接池中的最小空閑連接 默認 0
      

      另外還有額外的配置類RedisConfig.java

      package com.butterflytri.config;
      
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.data.redis.connection.RedisConnectionFactory;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
      import org.springframework.data.redis.serializer.RedisSerializer;
      import org.springframework.data.redis.serializer.StringRedisSerializer;
      
      /**
       * @author: WJF
       * @date: 2020/5/24
       * @description: RedisConfig
       */
      
      @Configuration
      public class RedisConfig {
      
          /**
           * redis鍵值對的值的序列化方式:通用方式
           * @return RedisSerializer
           */
          private RedisSerializer redisValueSerializer() {
              return new GenericJackson2JsonRedisSerializer();
          }
      
          /**
           * redis鍵值對的健的序列化方式:所有的健都是字符串
           * @return RedisSerializer
           */
          private RedisSerializer redisKeySerializer() {
              return new StringRedisSerializer();
          }
      
          @Bean("redisTemplate")
          public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
              RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
              redisTemplate.setConnectionFactory(redisConnectionFactory);
              redisTemplate.setKeySerializer(redisKeySerializer());
              redisTemplate.setValueSerializer(redisValueSerializer());
              return redisTemplate;
          }
      
      }
      
    • 4.3 redisTemplate的使用

      value類型的值的CRUD:

      ValueServiceImpl.java

      package com.butterflytri.service.impl;
      
      import com.butterflytri.service.ValueService;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      
      /**
       * @author: WJF
       * @date: 2020/5/27
       * @description: ValueServiceImpl
       */
      @Service
      public class ValueServiceImpl implements ValueService {
      
          @Resource
          private RedisTemplate<String, Object> redisTemplate;
      
          @Override
          public void addValue(String key, Object value) {
              redisTemplate.opsForValue().set(key,value);
          }
      
          @Override
          public Object get(String key) {
              return redisTemplate.opsForValue().get(key);
          }
      
          @Override
          public Object update(String key, Object newValue) {
              return redisTemplate.opsForValue().getAndSet(key,newValue);
          }
      
          @Override
          public void delete(String key) {
              redisTemplate.delete(key);
          }
      }	
      

      List類型的值的CRUD:

      這裏我加了枚舉類型用來控制增加的位置,因為List類型對應的是鏈表。

      ListServiceImpl.java

      package com.butterflytri.service.impl;
      
      import com.butterflytri.enums.OpsType;
      import com.butterflytri.service.ListService;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      import java.util.List;
      
      /**
       * @author: WJF
       * @date: 2020/5/28
       * @description: ListServiceImpl
       */
      @Service
      public class ListServiceImpl implements ListService {
      
          @Resource
          private RedisTemplate<String, Object> redisTemplate;
      
          @Override
          public void addList(String key, List<Object> list, OpsType type) {
              switch (type) {
                  case RIGHT:
                      redisTemplate.opsForList().rightPushAll(key, list);
                      break;
                  case LEFT:
                      redisTemplate.opsForList().leftPushAll(key, list);
                      break;
                  default:
                      throw new RuntimeException("type不能為null");
              }
          }
      
          @Override
          public void add(String redisKey, Object value, OpsType type) {
              switch (type) {
                  case RIGHT:
                      redisTemplate.opsForList().rightPush(redisKey, value);
                      break;
                  case LEFT:
                      redisTemplate.opsForList().leftPush(redisKey, value);
                      break;
                  default:
                      throw new RuntimeException("type不能為null");
              }
          }
      
          @Override
          public List<Object> get(String key) {
              return redisTemplate.opsForList().range(key, 0, -1);
          }
      
          @Override
          public Object update(String key, Object value, Integer index) {
              Object obj = redisTemplate.opsForList().index(key, index);
              redisTemplate.opsForList().set(key,index,value);
              return obj;
          }
      
          @Override
          public void delete(String key) {
              redisTemplate.delete(key);
          }
      
          @Override
          public void deleteValue(String redisKey, OpsType type) {
              switch (type) {
                  case RIGHT:
                      redisTemplate.opsForList().rightPop(redisKey);
                      break;
                  case LEFT:
                      redisTemplate.opsForList().leftPop(redisKey);
                      break;
                  default:
                      throw new RuntimeException("type不能為null");
              }
          }
      }
      

      Hash類型的值的CRUD:

      hash類型是我們使用最常用的類型。

      HashServiceImpl.java:

      package com.butterflytri.service.impl;
      
      import com.butterflytri.service.HashService;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      import java.util.Map;
      
      /**
       * @author: WJF
       * @date: 2020/5/28
       * @description: HashServiceImpl
       */
      @Service
      public class HashServiceImpl implements HashService {
      
          @Resource
          private RedisTemplate<String, Object> redisTemplate;
      
          @Override
          public void addHashAll(String key, Map<String, Object> value) {
              redisTemplate.opsForHash().putAll(key, value);
          }
      
          @Override
          public void addHash(String redisKey, String key, Object value) {
              redisTemplate.opsForHash().put(redisKey, key, value);
          }
      
          @Override
          public Object get(String redisKey, String key) {
              return redisTemplate.opsForHash().get(redisKey, key);
          }
      
          @Override
          public Object update(String redisKey, String key, Object value) {
              Object obj = this.get(redisKey, key);
              this.delete(redisKey,key);
              redisTemplate.opsForHash().put(redisKey, key, value);
              return obj;
          }
      
          @Override
          public void delete(String redisKey, String key) {
              redisTemplate.opsForHash().delete(redisKey, key);
          }
      
          @Override
          public void deleteAll(String redisKey) {
              redisTemplate.delete(redisKey);
          }
      }
      

      Set的值的CRUD:

      package com.butterflytri.service.impl;
      
      import com.butterflytri.service.SetService;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      import java.util.Set;
      
      /**
       * @author: WJF
       * @date: 2020/5/28
       * @description: SetServiceImpl
       */
      @Service
      public class SetServiceImpl implements SetService {
      
          @Resource
          private RedisTemplate<String, Object> redisTemplate;
      
      
          @Override
          public void addAll(String key, Set<Object> set) {
              redisTemplate.opsForSet().add(key,set);
          }
      
          @Override
          public void add(String key, Object value) {
              redisTemplate.opsForSet().add(key,value);
          }
      
          @Override
          public Set<Object> findAll(String key) {
              return redisTemplate.opsForSet().members(key);
          }
      
          @Override
          public void deleteValue(String key, Object value) {
              redisTemplate.opsForSet().remove(key,value);
          }
      
          @Override
          public void delete(String key) {
              redisTemplate.delete(key);
          }
      }
      

      ZSet類型的值的CRUD:

      package com.butterflytri.service.impl;
      
      import com.butterflytri.service.SortedSetService;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      import java.util.LinkedHashSet;
      
      /**
       * @author: WJF
       * @date: 2020/5/28
       * @description: SortedSetServiceImpl
       */
      @Service
      public class SortedSetServiceImpl implements SortedSetService {
      
          @Resource
          private RedisTemplate<String, Object> redisTemplate;
      
          @Override
          public void add(String key, String value, Double score) {
              redisTemplate.opsForZSet().add(key, value, score);
          }
      
          @Override
          public LinkedHashSet<Object> findAll(String key) {
              return (LinkedHashSet<Object>) redisTemplate.opsForZSet().range(key,0,-1);
          }
      
          @Override
          public Long count(String key, Double scoreFrom, Double scoreTo) {
              return redisTemplate.opsForZSet().count(key,scoreFrom,scoreTo);
          }
      
          @Override
          public LinkedHashSet<Object> findByScore(String key, Double scoreFrom, Double scoreTo) {
              return (LinkedHashSet<Object>) redisTemplate.opsForZSet().rangeByScore(key,scoreFrom,scoreTo);
          }
      
          @Override
          public Long rank(String key, Object value) {
              return redisTemplate.opsForZSet().rank(key,value);
          }
      
          @Override
          public void remove(String key, String value) {
              redisTemplate.opsForZSet().remove(key,value);
          }
      
          @Override
          public void delete(String key) {
              redisTemplate.delete(key);
          }
      
      }
      

      redis的Java客戶端有很多,在這裏我們使用的是jedis,還有一個很好的Java語言的客戶端叫lettuce,大家可以去了解一下,Spring從不重複造輪子,只會簡化輪子的使用,redisTemplate就是一個超級簡單的使用實現。到這裏redis整合Spring Boot 就結束了。

    5. 項目地址

    本項目傳送門:

    • GitHub —> spring-data-redis
    • Gitee —> spring-data-redis

    此教程會一直更新下去,覺得博主寫的可以的話,關注一下,也可以更方便下次來學習。

    • 作者:Butterfly-Tri
    • 出處:Butterfly-Tri個人博客
    • 版權所有,歡迎保留原文鏈接進行轉載

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

    聚甘新

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

    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內核源代碼情景分析》

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

    【其他文章推薦】

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

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

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

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

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

    聚甘新

  • 如何只用5分鐘完成數據 列表、創建頁面

    如何只用5分鐘完成數據 列表、創建頁面

    前言

    我們當然希望能夠更快的完成我們的工作,這樣我們才能有更多的時間做其他的事情,比如說測試、學習、放鬆。

    背景

    軟件一般也就這麼幾個方面的工作要做,增、刪、改、查。如果歸結到頁面上來說,那麼無非也就這麼幾個頁面Form頁面(增)、列表頁面(查、刪)、編輯頁面(改)。很大程度上,你的項目就是由不同的實體的這麼幾個頁面組裝起來的。既然他們都是這麼幾個頁面,那麼,我們是不是可以考慮針對這幾個頁面進行抽象呢?然後使用數據描述這幾個頁面的行為。

    效果

    經典倒敘,先上效果圖

    列表頁面

    創建頁面

    目前就簡單實現了列表頁面和創建頁面。編輯頁面,跟創建頁面太像了。暫時還沒有實現相關內容,不過,這個不是很重要了。

    實現過程

    需求分析

    其實,每個頁面都是存在固定的路數的。

    比如說:

    列表頁面裡邊主要存在這麼幾個參數:列表名、列表頭上的按鈕、列表的表頭、列表內容、列表每一行中的操作、分頁控件。

    表單頁面列表主要存在這麼幾個參數:表單名、表單內容項。

    主要的參數出現的位置都是固定的。但是什麼地方出現什麼內容則是可以變化的,一般情況下,我們都是通過代碼,一遍一遍的重寫這些頁面,然後來達到不同的應用之間的變化的目的。其實我們是可以通過數據來描述他們的。比如說向下面這樣。

    列表頁面的定義

    Form表單頁面的定義

    原始數據的定義

    然後將這些定義好的屬性通過後端渲染到頁面上。

    就可以達到,前邊展示的這種效果了。

    數據存儲

    因為數據類型是自定義的,所以數據存儲的字段也是可以自己隨便預設的。然後系統就可以直接支持這一數據類型。在這個Demo裡邊,我是簡單粗暴的使用了文件存儲Json文件的方式來進行保存的數據。

    其實應該鏈接數據庫的。不過我在Demo項目裡邊留下了相關的接口,只要再實現一個數據庫版本的實例就可以無縫對接了。

    其實

    當然了這隻是他的最初級的形態,因為現在寫的配置文件都是通過手寫來實現,將來可以做一個編輯器。並且可以實時看到調整過的效果。

    其實這個做法,是來源於PaaS項目中的一個很小很小的功能塊。真正的PaaS項目這一整套東西都是在線上直接編輯看效果的。

    最後

    系列

    這個項目將來會融入到我寫的PaaS Demo中作為前端展示部分。 系列的目錄在 https://juejin.im/post/5eca2a186fb9a047e96b2884 這個部分會一點點完善。

    開源

    雖然東西不大,但是還是希望能給你一點點啟發。 項目地址 https://gitee.com/anxin1225/Dov.GenericWeb

    簡單的體驗

    部署到雲端了,可以簡單體驗一下。

    http://gw.ash50p.com/Generic/Meeting.Record/List

    轉載莫忘原文地址:https://juejin.im/post/5eeb85b8e51d45740850f755

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

    【其他文章推薦】

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

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司“嚨底家”!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    聚甘新

  • 荷蘭開放首條塑膠再製自行車道 約含50萬個瓶蓋

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

    荷蘭人以愛好騎單車聞名,該國約 1,700 萬人,卻擁有超過 2,200 萬輛腳踏車,光是阿姆斯特丹就鋪設了近 800 公里的自行車道,東北部城鎮茲沃勒(Zwolle)日前又正式開放一條全由回收塑膠再製生成的自行車道。

    該塑膠車道概念來自道路工程公司 KWS 的 Anne Koudstaal 和 Simon Jorritsma,道路全長 30 公尺,採用預製模塊,重量輕、易安裝,下方設計了排水系統讓管道、電纜通過,可讓水快速流通,遇到暴雨時還能充當臨時儲水槽、避免淹水。

    據估計,這條自行車道包含了約 218,000 個塑膠杯,或 50 萬個塑膠瓶蓋,使用年限可比傳統道路長 2~3 倍(有待觀察),路面應該也不會出現裂縫或坑洞。

     

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

  • 德國小城弗萊堡 交通轉型有成 35年私人汽車減半

    環境資訊中心特約記者 陳文姿報導

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

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司“嚨底家”!

    ※推薦評價好的iphone維修中心

    聚甘新

  • 印度雨季後氣候乾燥 德里登革熱疫情飆升

    摘錄自2018年09月18日中央社報導

    「印度斯坦時報」(Hindustan Times)18日引述南德里市政機構(SDMC)彙整新德里、東德里和北德里等另3個市政機構的統計顯示,德里地區今年迄今的登革熱病例達243例。

    在15日之前的一個星期,德里地區就通報了106起登革熱病例,高達全年迄今243例的近一半。這段時間是德里近2個月連續降雨以來,首個未降雨的星期。而雨季後出現的乾燥氣候,專家認為是登革熱病例突然飆升的主因。醫護人員和專家都擔心,最近登革熱病例的上升,現在只是開端。

    為防止登革熱疫情傳布,南德里市政機構最近已對社區大規模噴灑殺蟲劑。北德里市政機構也表示,正派檢查員調查和處罰未清理積水者。

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

    ※超省錢租車方案

    聚甘新

  • 氣候模擬:廣設風光發電 有助撒哈拉沙漠綠化

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

    聚甘新

  • 黑龍江大豆因霜害減產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/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

    聚甘新

  • 非洲豬瘟來源 陸專家疑世界盃肉品帶回病毒

    摘錄自2018年9月19日中央社報導

    針對非洲豬瘟傳進中國途徑,中國科學家現推斷懷疑,有可能是陸客前往俄羅斯觀看世界盃,所購買並帶回中國的肉製品帶有病毒,因未食用丟棄成為餿水,後被拿去餵養生豬引發疫情。

    中國農業科學院哈爾濱獸醫研究所副研究員孫元指出,即便未通過俄羅斯世界盃這個可能管道擴散到中國,以後也會由其他路徑而來。他表示,2014年相關研究顯示中國當時確實沒有非洲豬瘟病毒,如今一個月內就在中國多地出現,蔓延之快令人警惕。

    孫元也認為,蔓延原因或許跟病毒的生命力強有關。非洲豬瘟病毒耐高溫,耐pH值範圍廣,在血液、糞便和組織裡可長期存活,凍肉裡可存活數年乃至數十年,未熟肉、醃肉、泔水(餿水)、下腳料中可長時間存活。

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

    【其他文章推薦】

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

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司“嚨底家”!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    聚甘新

  • 車商化敵為友挑戰Tesla,共同發展電動車充電網

    車商化敵為友挑戰Tesla,共同發展電動車充電網

    市場上能見度高的汽車廠皆已跨入電動車領域,電動車市場顯得更加蓬勃,競爭也更加激烈,但也為車商創造了前所未見的合作組合。BMW、Diamler、Ford、Volkswagen旗下的Audi與Porsche宣布將在歐洲共同發展電動車充電網。

    歐洲是全球最早開始發展電動車的地區之一,荷蘭、挪威等國都是電動車高普及國。雖然歐洲因人口與市場發展較早等因素,未來幾年的成長率預期將落後於中國、美國等地,但仍然是電動車產業的一大標的市場。

    BBC報導,國際車商BMW、Diamler、Ford和Volkswagen將聯手在歐洲發展高效能的電動車充電網絡,預計將在全歐設置400處充電站。這些充電站將沿著主要道路設置,並為電動車提供超高速充電服務。這套充電網絡的基礎是一組結合式的標準充電系統科技,車主最高可享有350kW的充電功率,是目前市場上充電樁效能最高的設備。

    不過,Tesla電動車因系統不相容,將無法使用這些充電站。

    這些合作車商表示,他們希望發展一套裝置快速、可快速擴充充電站數量的系統,讓電動車車主們能享受遠程駕駛,不受電池續航力所限制。而這項合作將在2017年正式起跑。

    將帶來車商與車主雙贏

    電動車充電APP軟體Zap-Map的執行者Ben Lane對BBC表示,這是一個「超讚的消息,這正是產業需要的。」他指出,隨著電動車搭載的電池變大,充電時間也會越長;沿路都有快速充電系統對車主來說是個利多。

    BBC則舉加油站的廣泛設置為例,說明這四間車商宣布將聯手打造綿密的電動車充電網的行動,是非常合理的;這不僅能提升電動車車主的使用方便性,也能說服更多人購買電動車。

    雖然目前電動車充電時間仍不如直接加油來得快,但Tesla的超級快充站已經可在40分鐘內將Model S的電池充到八分飽,更有計畫將所需時間降到5~10分鐘。若BMW等四間車商能透過通力合作的方式發展更具效率的充電設備,電動車就會隨著這個計畫變得更具競爭力。

    (照片來源:維基共享資源)

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

    【其他文章推薦】

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

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

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

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

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

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

    聚甘新