cuốn sách gpt4 ai đã làm

Phương pháp xử lý giao thức Big-endian dựa trên các trường bit thứ tự ngược

In lại Tác giả: Hãy đến và chào đón Thời gian cập nhật: 28-01-2024 22:24:17 26 4
mua khóa gpt4 Nike

Tổng quan

Bài viết này chủ yếu mô tả cách xử lý phương thức chuyển đổi big-endian trong giao thức truyền thông một cách đơn giản trong phần mềm C/C++ do hạn chế về tài nguyên nên hiện chỉ được kiểm chứng trong một số trình biên dịch và CPU trên nền tảng Windows.

Endian lớn và nhỏ

Đơn vị lưu trữ dữ liệu cơ bản trong bộ nhớ là Byte và đơn vị lưu trữ nhỏ nhất là bit Trong kiến ​​trúc ARM thường được sử dụng, khi dữ liệu được xử lý theo byte, dữ liệu luôn ở thứ tự thấp nhất và thứ tự cao nhất trong bộ nhớ. phía trước LSB (bit ít quan trọng nhất, LSB); trong khi lượng dữ liệu có thể được biểu thị bằng một Byte bị hạn chế, C/C++ đã mở rộng nhiều loại cơ bản được biểu thị bằng nhiều Byte, chẳng hạn như int, uint32_t, v.v. . Có hai cách để CPU lưu trữ nhiều Byte trong bộ nhớ, đó là chế độ big-endian và chế độ little-endian. Cái gọi là chế độ big-endian có nghĩa là các byte bậc cao được đặt ở đầu địa chỉ thấp của bộ nhớ. và các byte thứ tự thấp được đặt ở đầu cao của bộ nhớ. Cái gọi là chế độ endian nhỏ có nghĩa là các byte bậc thấp được sắp xếp ở đầu địa chỉ thấp của bộ nhớ và các byte bậc cao được sắp xếp ở đầu địa chỉ cao của bộ nhớ. Ví dụ: số 0x12 34 56 78 được biểu thị trong bộ nhớ dưới dạng:

MSB

Byte 0 1 2 3
dữ liệu 0x12 0x34 0x56 0x78

LSB

Byte 0 1 2 3
dữ liệu 0x78 0x56 0x34 0x12

Cách xác minh độ bền lớn và nhỏ

Để xác minh độ bền của CPU, bạn có thể sử dụng con trỏ hoặc liên kết để xác định giá trị cụ thể của byte đầu tiên của số không dấu 32 bit, phương pháp như sau:

#include "stdio.h" #include "stdint.h" union MsbVerify { uint32_t m_word; uint8_t m_bytes[sizeof(uint32_t)]; int main() { union MsbVerify _verify_code; m_byte[0] == 0x12) { printf("MSB\n"); } else if(_verify_code.m_bytes[0] == 0x78) { printf("LSB\n" } return 0;

Ưu điểm và nhược điểm của endian lớn và nhỏ

Từ góc độ con người, việc lưu trữ dữ liệu của big endian phù hợp hơn với thói quen đọc của con người từ trái sang phải nhưng từ góc độ máy tính, endian nhỏ có nhiều lợi thế hơn trong việc xử lý dữ liệu.

  1. Mở rộng thuận tiện Khi máy tính lưu trữ một số không dấu uint8_t trong phạm vi 256, nó chỉ cần một byte để biểu thị số đó; khi vượt quá phạm vi này, ở chế độ little-endian, nó chỉ cần thêm một Byte vào địa chỉ cuối cùng để mở rộng nó thành uint16_t. . Như hình bên dưới, khi dữ liệu được mở rộng từ 0x0034 lên 2Byte 0x1234, chế độ little endian chỉ cần điền dữ liệu vào địa chỉ 1 đến 0x12;

    Địa chỉ 0 1 => 0 1
    dữ liệu 0x34 0x00 => 0x34 0x12

    Đối với dữ liệu lớn, trước tiên bạn cần di chuyển dữ liệu ở địa chỉ 0 sang địa chỉ 1, sau đó điền dữ liệu vào địa chỉ 0:

    Địa chỉ 0 1 => 0 1 => 0 1
    dữ liệu 0x34 0x00 => 0x00 0x34 => 0x12 0x34

    So với chế độ endian nhỏ, chế độ endian lớn yêu cầu nhiều bước thao tác bộ nhớ hơn để hoàn thành việc truyền dữ liệu. Các thao tác endian lớn sẽ tiêu tốn nhiều chu kỳ lệnh CPU hơn, dẫn đến giảm hiệu quả truy cập bộ nhớ liên quan đến các thao tác đó;

  2. Cách tính tương tự như mục 1. Khi CPU tính toán dữ liệu, lấy phép tính tự tăng tương tự làm ví dụ, chế độ endian nhỏ chỉ cần thực hiện tự tăng ở địa chỉ 0 trong khi big endian cần; để di chuyển địa chỉ tương ứng với Byte theo độ dài dữ liệu, sau đó Thực hiện tự tăng; quá trình đánh địa chỉ như vậy cũng chiếm các chu kỳ lệnh của CPU, dẫn đến hiệu suất giảm;

Để biết chi tiết, bạn có thể tham khảo video của ông chủ epcdiy và tôi sẽ không tiếp tục mở rộng ở đây. Tại sao những thiết kế kỳ lạ từ 50 năm trước vẫn được sử dụng trên điện thoại di động và máy tính hiện đại?

trường bit

Định nghĩa và cách sử dụng các trường bit

Để biết định nghĩa và cách sử dụng các trường bit, vui lòng tham khảo Bách khoa toàn thư Baidu.

Căn chỉnh

Do những hạn chế của bus CPU, lấy CPU 32 bit làm ví dụ, bus bộ nhớ của nó là 32 bit, nghĩa là một chu kỳ lệnh của CPU có thể đọc bộ nhớ 32 bit tương ứng với RAM, nếu có; cũng được truy cập bằng khối bộ nhớ 32 bit nên hiệu quả luôn đạt cao nhất. Trong thiết kế bộ nhớ, địa chỉ luôn bắt đầu từ 0, có nghĩa là CPU truy cập bộ nhớ từ bội số của 4 byte mỗi lần; sau đó, khi dữ liệu được chia thành hai bit 32 bit, CPU phải truy cập bộ nhớ đó hai lần. có được dữ liệu đầy đủ này. Do đó, để đảm bảo hiệu quả, trình biên dịch C thường đặt các biến được xác định của cấu trúc ở một vị trí mà CPU có thể truy cập bằng cách đọc bộ nhớ cùng một lúc; hiệu suất tương ứng với địa chỉ bộ nhớ là căn chỉnh 4 byte.

Đề cập đến thiết kế cấu trúc sau đây, sự sắp xếp bộ nhớ cuối cùng của nó sẽ như thế nào?

struct MemAlignTest { uint16_t data1; uint16_t data2; uint8_t data4;

Theo mô tả trong định nghĩa của nó, kích thước của cấu trúc này phải là 22 byte; theo ý định chủ quan của nhà phát triển, hy vọng rằng những dữ liệu này có thể lấp đầy toàn bộ không gian bộ nhớ theo thứ tự từ địa chỉ nhỏ đến địa chỉ lớn, tức là được sắp xếp như sau; :

Địa chỉ cơ sở\offset 0x00 0x01 0x02 0x03
0x00000000 dữ liệu0_B0 dữ liệu0_B1 dữ liệu1 dữ liệu2_B0
0x00000004 dữ liệu2_B1 dữ liệu3_B0 dữ liệu3_B1 dữ liệu3_B2
0x00000008 dữ liệu3_B3 mảng[0] mảng[1] mảng[2]
0x0000000C dữ liệu4_B0 dữ liệu4_B1 dữ liệu5_B0 dữ liệu5_B1
0x00000010 dữ liệu5_B2 dữ liệu5_B3 dữ liệu5_B4 dữ liệu5_B5
0x0000000C dữ liệu5_B6 dữ liệu5_B7 dự trữ dự trữ

Kiểm tra nó bằng đoạn mã sau:

    struct MemAlignTest Align_test ; .mảng[1] = 0x22; căn chỉnh_test.array[2] = 0x33; căn chỉnh_test.data4 = 0xA9A9; căn chỉnh_test.data5 = 0x1234567800ABCDEF("Kích thước MemAlignTest:%d\n",sizeof(align_test)); 8,4);

Đầu ra là:

Có thể thấy rằng độ dài dữ liệu thực tế nhỏ hơn 32 byte, khác 10 byte so với 22 byte mà chúng tôi mong đợi. Điều này có nghĩa là một số dự trữ căn chỉnh do người dùng không xác định sẽ bị buộc phải chèn vào một số byte do cơ chế căn chỉnh; bằng cách phân tích một số số tức thời được đặt trước, chúng ta có thể biết rằng cách sắp xếp bộ nhớ thực tế như sau:

Địa chỉ cơ sở\offset 0x00 0x01 0x02 0x03
0x00000000 0x34 0x12 0x88 0xAA
0x00000004 0xCC 0xCC 0xAA 0xAA
0x00000008 0xFF 0xEE 0xDD 0xCC
0x0000000C 0x11 0x22 0x33 0xAA
0x00000010 0xA9 0xA9 0xAA 0xAA
0x00000014 0xAA 0xAA 0xAA 0xAA
0x00000018 0xEF 0xCD 0xAB 0x00
0x0000001C 0x78 0x56 0x34 0x12

Trước khi hiểu cách sắp xếp bộ nhớ thực tế, chúng ta cần hiểu một số quy tắc căn chỉnh cơ bản:

  1. Trình biên dịch hy vọng rằng CPU có thể đọc tất cả dữ liệu của một biến thông qua một lần truy cập bộ nhớ;
  2. Khi trình biên dịch xử lý việc căn chỉnh dữ liệu, địa chỉ cơ sở lưu trữ dữ liệu phải là bội số nguyên của độ dài dữ liệu;

Nhưng trên thực tế, theo sự sắp xếp dự kiến ​​của nhà phát triển, địa chỉ 0x0000_0003 của trường bit cục bộ và địa chỉ 0x0000_0004 được lưu trữ trong data2 không thể được đọc cùng một lúc với khả năng đánh địa chỉ của CPU 32bit. Dữ liệu hoàn chỉnh của ata2, do đó trình biên dịch chèn 1 byte byte dự trữ căn chỉnh vào địa chỉ 0x0000_0003; tương tự, quy tắc này được tuân theo cho đến 0x0000_0010, là vị trí lưu trữ của data4. Khi xử lý data4, trình biên dịch cũng tuân theo quy tắc 1 và đặt dữ liệu vào địa chỉ 0x0000_0010 và dự trữ 2 byte ở vị trí 0x0000_0012~0x0000_0013 nhưng vì dữ liệu; 5 là dữ liệu 64bit. Theo quy tắc 2, vì 0x0000_0014 không phải là bội số nguyên của 8 nên trình biên dịch sẽ tiếp tục dự trữ 4 byte để đảm bảo rằng data5 có thể được điền vào địa chỉ cơ sở 0x0000_0018. Vì vậy, sự sắp xếp thực sự của cấu trúc này trong bộ nhớ sẽ là:

Địa chỉ cơ sở\offset 0x00 0x01 0x02 0x03
0x00000000 dữ liệu0_B0 dữ liệu0_B1 dữ liệu1 dự trữ
0x00000004 dữ liệu2_B0 dữ liệu2_B1 dự trữ dự trữ
0x00000008 dữ liệu3_B0 dữ liệu3_B1 dữ liệu3_B2 dữ liệu3_B3
0x0000000C mảng[0] mảng[1] mảng[2] dự trữ
0x00000010 dữ liệu4_B0 dữ liệu4_B1 dự trữ dự trữ
0x00000014 dự trữ dự trữ dự trữ dự trữ
0x00000018 dữ liệu5_B0 dữ liệu5_B1 dữ liệu5_B2 dữ liệu5_B3
0x0000001C dữ liệu5_B4 dữ liệu5_B5 dữ liệu5_B6 dữ liệu5_B7

Với sự sắp xếp hợp lý của trình biên dịch, CPU luôn có thể lấy được các giá trị thành viên trong cấu trúc trong một lần truy cập bộ nhớ.

cách tiếp cận chặt chẽ

Nếu bạn không muốn trình biên dịch định dạng dữ liệu theo cách này, bạn có thể chuyển.

#pragma pack(1) /*Định nghĩa cấu trúc*/ struct _sname { /*……*/ }; #pragma pack()

Xác định cấu trúc; giữa các khoảng khai báo của hai gói, trình biên dịch sẽ sắp xếp cấu trúc theo cách căn chỉnh 1 byte.

Phương thức truyền giao thức Big-endian dựa trên các trường bit thứ tự ngược

Một tuyên bố chống thanh được viết ở phần đầu: Lưu ý: Các quy tắc sau chỉ được thử nghiệm trên nền tảng ARM của arm-none-eabi-gcc v5.3 và CygWin. Các kịch bản tương tự khác cần được đánh giá dựa trên khả năng thích ứng của chúng và không phải vậy. hoàn toàn có thể mang theo được; có thể hiểu là chỉ được sử dụng trong trường hợp đặc biệt.

Mô tả kịch bản sử dụng

Các phương thức được khai báo bên dưới chủ yếu dựa vào các trường bit. Tuy nhiên, chỉ một số ngôn ngữ lập trình có chức năng này và có sự khác biệt trên các nền tảng khác nhau, CPU khác nhau và trình biên dịch khác nhau. nền tảng cần được xác minh trước, bao gồm nhưng không giới hạn ở:

  1. Phương thức căn chỉnh của trường bit là căn chỉnh 4Byte, 8Byte, v.v.;
  2. Trình biên dịch có hỗ trợ byte mở rộng trường bit hay không;
  3. Các phương pháp mã hóa endian lớn và nhỏ được CPU hỗ trợ;
  4. Giao thức được thiết kế theo số lượng byte được căn chỉnh;

Ví dụ: trong MSVC, do các giới hạn của trường bit; các định nghĩa trường bit trên các loại cơ bản sẽ vẫn được căn chỉnh, khiến cấu trúc trường bit khác với cấu trúc thiết kế, điều này hạn chế các phương thức chuyển đổi sau.

nguyên tắc

Do một số lý do lịch sử và chủ quan, trong các thiết bị nhúng, hầu hết các giao thức tùy chỉnh đều sử dụng big endian để truyền; phương thức truyền big endian chắc chắn có những ưu điểm không thể thay thế khi lập trình viên nắm bắt và phân tích dữ liệu được truyền nhưng khi sử dụng lập trình giao thức ARM In cho các endian chính thống; CPU, việc chuyển đổi dữ liệu được lưu trữ trong bộ nhớ endian nhỏ thành dữ liệu endian lớn trong giao thức là vô cùng bất tiện. Trong hầu hết các trường hợp, các nhà phát triển cần thực hiện xử lý mặt nạ và dịch chuyển từng bit cho giao thức.

Trong thiết kế bus truyền dữ liệu chung, chẳng hạn như UART, CAN, SPI và I2C, v.v., hầu hết chúng được truyền trong lớp vật lý theo thứ tự bit MSB; nghĩa là, trong các bus tương tự này, thứ tự tương tự như truyền 0x1A là 0b00011010b; ; chúng tôi sử dụng vật lý Thảo luận về trường hợp của hầu hết các bộ xử lý ARM trong đó lớp được truyền MSB và bộ nhớ lưu trữ ít endian.

Hướng truyền như sau, lấy 0x1A làm ví dụ:

Hướng truyền =======>
dữ liệu 0 0 0 1 1 0 1 0
  • Phương pháp truyền dữ liệu 32Bit

Trong bộ nhớ endian nhỏ, dữ liệu 32 bit 0x12345678 thực sự được lưu trữ ở dạng:

byte Byte0 Byte1 Byte2 Byte3
HEX 0x78 0x56 0x34 0x12
Thùng 0b01111000 0b01010110 0b00110100 0b00010010

Nếu giao thức yêu cầu truyền endian lớn, Byte0 và Byte3 cần được đảo ngược, còn Byte2 và Byte1 cần được đảo ngược và truyền trong truyền MSB. Do đó, khi truyền, nếu MSB của Byte3 cần được truyền trước khi truyền ở big endian, thì chúng ta có thể chuyển đổi dữ liệu 32 bit thành mảng 8 bit thông qua một con trỏ hoặc liên kết và truyền nó từ byte cuối cùng. quan sát hướng truyền Bit và hướng truyền dữ liệu như sau:

Thứ tự bit => => => =>
endian nhỏ 0x78 0x56 0x34 0x12
Độ bền <= <= <= <=

Khi chuyển sang dạng endian lớn phổ quát, có thể thấy đường truyền như sau:

Thứ tự bit => => => =>
endian nhỏ 0x12 0x34 0x56 0x78
Độ bền => => => =>
  • Trường bit mở rộng

Như có thể thấy từ phần mô tả trường bit nói trên, dữ liệu luôn được xếp chồng từ byte thấp đến byte cao, nghĩa là dữ liệu cũng có thể được sắp xếp theo thứ tự ngược lại và được gửi từ byte cao nhưng câu hỏi đặt ra là liệu có giới hạn nào cho việc này không; sắp xếp?

Tất cả các mã sau đây đều được chạy trong cygwin. Chúng ta hãy hiển thị cấu trúc dữ liệu 32 bit được căn chỉnh theo Byte như sau:

typedef struct { uint32_t datafield0 : 4; uint32_t datafield1 : 2; uint32_t datafield3 : 8;

Hãy cung cấp cho nó một số giá trị ban đầu và xem nó trông như thế nào trong bộ nhớ từ góc độ Byte:

int i = 0; TestStruct temp; uint8_t* ptr = (uint8_t*)&temp; temp.datafield0 = 0xC; temp.datafield2 = 0x2; temp.datafield4 = 0x1234; tôi = 0; tôi < sizeof(temp); { printf("0x%.2X ",ptr[i]); } printf("\n");

Đầu ra như sau:

Có thể thấy rằng trường dữ liệu0 được đặt ở 4 bit thấp hơn của Byte0, còn trường dữ liệu1 và trường dữ liệu2 được hợp nhất thành một 0x9 (0b1001) và được đặt ở 4 bit trên của Byte0. Khi chúng tôi gửi dữ liệu trước với địa chỉ byte cao, chúng tôi có thể. thấy rằng Thứ tự thu được chính xác là định dạng big-endian để đảo ngược dữ liệu của toàn bộ cấu trúc, nghĩa là định dạng để truyền big-endian như sau:

typedef struct { uint32_t datafield4 : 16; uint32_t datafield3 : 8; uint32_t datafield2 : 2; uint32_t datafield1 : 2;

Bằng cách tương tự, có thể thấy rằng quy tắc này phổ biến trong các cấu trúc trường bit có định nghĩa thực thể. Tức là: trong đường truyền vật lý MSB, khi dữ liệu được truyền tuần tự bắt đầu từ địa chỉ cao của cấu trúc trường bit thì định dạng truyền của dữ liệu trên bus vật lý là định dạng big-endian sau trình tự đảo ngược của từng trường trong trường bit.

giới hạn

Theo mô tả ở trên, khi C/C++ sử dụng các trường bit để xử lý giao thức, nó được xác định theo trình tự đảo ngược giao thức và khi dữ liệu được truyền từng byte từ địa chỉ byte cao đến địa chỉ byte thấp, trường bit và Bản chất của bus vật lý chuyển đổi dữ liệu thành big endian mà không cần phải viết nhiều thao tác dịch chuyển trong mã cực kỳ khó đọc và khó bảo trì. Tuy nhiên, phương pháp này vẫn còn nhiều hạn chế như:

  • Mảng trong cấu trúc không phù hợp với tính năng này Khi một mảng xuất hiện trong cấu trúc, nếu bạn chỉ thực hiện xử lý truyền ngược, bạn sẽ thấy rằng mặc dù dữ liệu trong mảng ở định dạng big-endian thông thường, nhưng sự sắp xếp của toàn bộ mảng cũng thay đổi về diện tích mảng. Nó trở thành một sự thay đổi từ chỉ mục lớn sang chỉ mục nhỏ. Vì vậy, khi một mảng xuất hiện trong cấu trúc truyền đi thì thứ tự các phần tử mảng cần được đảo ngược trước khi truyền đi.

  • Dữ liệu có độ dài thay đổi không phù hợp với tính năng này. Dữ liệu có độ dài thay đổi thường được lưu trữ ở các vị trí khác dưới dạng mảng hoặc con trỏ thay vì ở dạng cấu trúc, do đó, nội dung dữ liệu có độ dài thay đổi cần được phân đoạn và xử lý trong từng phân đoạn. phù hợp với tính năng này tương ứng.

  • Vị trí chưa được căn chỉnh bị đảo ngược. Khi cấu trúc dữ liệu không được căn chỉnh theo byte, giao thức được thiết kế theo thứ tự dương sẽ mặc định ở vị trí dành riêng ở cuối giao thức tại thời điểm này, nếu sử dụng #pragma pack(1) để khai báo; Căn chỉnh 1 byte, giao thức A ban đầu được xác định theo thứ tự sẽ đặt các trường dành riêng không được căn chỉnh ở cuối cấu trúc, tuy nhiên, trong thiết kế này, các trường không được căn chỉnh đó cần phải được khai báo rõ ràng trong tiêu đề cấu trúc theo thứ tự ngược lại, nếu không thì khi dữ liệu được sắp xếp; được truyền đi, trường đầu tiên được truyền đi là một số trường dành riêng không được thiết kế trong giao thức.

Phần kết luận

Bài đăng trên blog này chỉ đề xuất một phương pháp xử lý đơn giản và hiệu quả để chuyển đổi endian giao thức nhị phân được sử dụng trong trường nhúng. Tuy nhiên, phương pháp xử lý này có những hạn chế rất lớn bao gồm các yêu cầu rất cao về trình biên dịch, CPU, nền tảng phát triển và ngôn ngữ lập trình; trong quá trình cấy ghép, các nhà phát triển nhúng cần đánh giá đầy đủ các kịch bản sử dụng của riêng họ trước khi thiết kế, giải pháp được đề xuất trong bài đăng trên blog này cũng thiên về "các trường hợp đặc biệt và cách sử dụng đặc biệt" hơn; ; Quá trình cấy ghép và sử dụng đòi hỏi các nhà phát triển phải xử lý cẩn thận, đặc biệt là các tính năng của trình biên dịch và tính năng của nền tảng phát triển, có ảnh hưởng lớn đến phương pháp sử dụng cụ thể này cần phải được đánh giá trước khi đưa ra quyết định.

Cuối cùng, bài viết này về phương pháp xử lý giao thức big-endian dựa trên trường bit thứ tự ngược có ở đây. Nếu bạn muốn biết thêm về phương pháp xử lý giao thức big-endian dựa trên trường bit thứ tự ngược, vui lòng tìm kiếm CFSDN. bài viết hoặc tiếp tục Duyệt các bài viết liên quan, tôi hy vọng bạn sẽ ủng hộ blog của tôi trong tương lai! .

26 4 0
Chào mừng tất cả mọi người đã đến
Hồ sơ

Tôi là một lập trình viên xuất sắc, rất giỏi!

Nhận phiếu giảm giá taxi Didi miễn phí
Phiếu giảm giá taxi Didi
Chứng chỉ ICP Bắc Kinh số 000000
Hợp tác quảng cáo: 1813099741@qq.com 6ren.com
Xem sitemap của VNExpress