動機

整理一些之前的問題

extern, static, global

extern 的意思是去外面找,去其他的程式中找在哪 static 的意思是只在這個範圍存活&可見,用這個方式來看file(global)、function、class(only for c++)的static變數,就會很一致了

下面的三個code就是demo extern的用處

extern之後,a就可以用了 但是對於有static的global變數,linker會找不到他

aaa.cpp

int a = 10;
int b = 100;
static int c = 1000;

void x(){a--;}
void y(){b--;}

aaa.hpp

extern int b;

void x();
void y();

bbb.cpp

#include "aaa.hpp"
#include <iostream>
int main() {
	//std::cout << a; error
	std::cout << b << '\n';
	y();
	std::cout << "after y: " << b << '\n';
	b--;
	std::cout << "after b--: " << b << '\n';
	extern int a;
	std::cout << a;
	x();
	std::cout << "after a: " << a << '\n';
	a--;
	std::cout << "after a--: " << a << '\n';
  // extern static int c; // linker fails, cant find c
}

extern “C”

在C++中函數可以overload,所以C++的函數其實編譯出來後名字會被加料,但 如果要include一些用c寫好的函數就會出事,名字會對不上,所以要用extern "C"

extern "C"就是說這段不要加料,照C的方式來

use C lib in C++

/* fromc.h */
#ifdef __cplusplus
extern "C" int get10();
#else
int get10();
#endif

/* fromc.c */
int get10() {return 10;}
// useclib.cpp
#include <iostream>
#include "fromc.h"

int main() {
    std::cout << get10() << '\n';
}

use C++ lib in C

// fromcpp.hpp
int get20_for_C();

// fromcpp.cpp
int get20() {return 20;}

extern "C" {
    int get20_for_C() {return get20();}
}
/* usecpplib.c */
#include <stdio.h>
#include "fromcpp.hpp"

int main() {
    printf("%d\n", get20_for_C());
}

static 為什麼放在.data

程式在記憶體中會有

  1. stack&heap a. stack: 放local var與函數的call stack b. heap: 動態allocate的東西
  2. bss: uninit的static
  3. data: 放 全域變數與常數
  4. text: 程式碼

data 與 stack&heap 的差別是?

data的資料只要程式還在跑,就不會消失 stack&heap的資料隨時都有可能消失

所以static 為什麼放在.data? 因為static隨時都要存活,所以不能放在stack&heap,那就只能放在data

about C++ class

friend

struct A {
  void normal_method() {}
  friend void friend_method(A& me) {}
};
int main() {
  A a;
  //原本的method call
  a.normal_method();
  //不想放前面的method call,當然不一定是只能傳自己,這就是函數可以多個
  friend_method(a);
}

virtual

有標virtual就是會在runtime時依據實際上的type去找method執行,如果沒有就是依據變數的type

#include <iostream>

struct A {
void x() {std::cout << "x\n";}
virtual void y() {std::cout << "y\n";}
};

struct B : public A {
void x() {std::cout << "x2\n";}
virtual void y() {std::cout << "y2\n";}
};

int main() {
        A* a = new B();
        a->x(); // x
        a->y(); // y2
        ((B*)a)->x(); // x2
        delete a; 
}

注意: constructor不能是virtual constructor本來就是從new時的type建立回去,所以不需要virtual 另一個說法是new物件時還沒有virtual的table,所以不能是virtual

但destructor需要,因為如果像上面的code去刪一個parent指標,時沒有virtual,會從parent的destructor去跑,這就尷尬了

object slicing

就是從child轉到parent會讓child的東西不見,這就是slicing 但是,因為cpp有copy,事情可能會搞砸

如果從child到parent是用copy的話,就不會根據原本的資料去找attribute與method,同時如果要轉回去也會出事(要記得用dynamic_casr轉啊)

class Base
{
protected:
    int m_value{};
 
public:
    Base(int value) : m_value{ value } {}
 
    virtual const char* getName() const { return "Base"; }
    int getValue() const { return m_value; }
};
 
class Derived: public Base
{
public:
    Derived(int value) : Base{ value } {}
 
    virtual const char* getName() const { return "Derived"; }
};
 
void printName(const Base base) // note: base passed by value, not reference
{
    std::cout << "I am a " << base.getName() << '\n';
}

int main()
{
    Derived derived{ 5 };
    std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n';
 
    Base &ref{ derived }; // ok
    std::cout << "ref is a " << ref.getName() << " and has value " << ref.getValue() << '\n';
 
    Base *ptr{ &derived }; // ok
    std::cout << "ptr is a " << ptr->getName() << " and has value " << ptr->getValue() << '\n';

    Base base{ derived }; // be sliced
    std::cout << "base is a " << base.getName() << " and has value " << base.getValue() << '\n';

    printName(derived); // be sliced
    return 0;
}

the rule of five

如果定義了自己的

  1. destructor
  2. copy constructor
  3. copy assignment

就要把其他的也一起定義完

不過因為C++11有move,所以要多兩個

  1. move constructor
  2. move assignment
class rule_of_five
{
    char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block
 public:
    rule_of_five(const char* s = "")
    : cstring(nullptr)
    { 
        if (s) {
            std::size_t n = std::strlen(s) + 1;
            cstring = new char[n];      // allocate
            std::memcpy(cstring, s, n); // populate 
        } 
    }
 
    ~rule_of_five()
    {
        delete[] cstring;  // deallocate
    }
 
    rule_of_five(const rule_of_five& other) // copy constructor
    : rule_of_five(other.cstring)
    {}
 
    rule_of_five(rule_of_five&& other) noexcept // move constructor
    : cstring(std::exchange(other.cstring, nullptr))
    {}
 
    rule_of_five& operator=(const rule_of_five& other) // copy assignment
    {
         return *this = rule_of_five(other);
    }
 
    rule_of_five& operator=(rule_of_five&& other) noexcept // move assignment
    {
        std::swap(cstring, other.cstring);
        return *this;
    }
 
// alternatively, replace both assignment operators with 
//  rule_of_five& operator=(rule_of_five other) noexcept
//  {
//      std::swap(cstring, other.cstring);
//      return *this;
//  }
};

Edge Trigger & Level Trigger

  • Edge Trigger: 狀態變化時產生io事件
  • Level Trigger: 滿足條件時產生io事件

memory

  • memory hierarchy
    1. register
    2. cache memory
    3. main memory
    4. HDD
  • Direct Memory Access, DMA
    • 讓device可以直接操作記憶體,像是device把資料copy到mem去
      • 會導致cache不一致!! (因為直接改mem,但cache不知道)
        • 寫入mem時要通知cache做invalidate
        • 讀cache時,dma已經開始(拿到最新的) 或 dma關閉(不會被改)
    • 不然cpu就要中斷自己去做
    • 由cpu啟動dma,剩下的事情就是dma controller的工作

mutex & semaphore

  • mutex
    1. 受益人數: 一人
    2. 誰能改變狀態(上鎖): a. 還沒上鎖: 所有人 b. 上鎖了: 上鎖的人
    3. 使用場域: 保護critical zone
  • semaphore
    1. 受益人數: 看設定多少
    2. 誰能改變狀態(記數): 誰都ok
    3. 使用場域: 同步時的signal

Big Endian & Little Endian

資料放進記憶體中的時

  • Big-Endian(Network Order): 最高位的位元組會放在最低的記憶體位址上
  • Little-Endian: 最高位的位元組放在最高的記憶體位址上

記憶體的最高在右邊,但資料的最高在左手邊

High -> 12345678 <- Low
Low -> a[0], a[1], a[2], a[4] <- High
#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>
typedef union {
  uint32_t l;
  unsigned char c[4];
} EndianTest;

// 輸出位元組順序
void printBytes(uint32_t x) {
  EndianTest et;
  et.l = x;
  for (int i = 0; i < 4; i++) {
    printf("0x%02X ", et.c[i]);
  }
  printf("n");
}

int main() {
  uint32_t x = 0x12345678;
  // big: 12, 34, 56, 78
  // little: 78, 56, 34, 12
  printf("0x%X 在記憶體中的儲存順序:", x);
  printBytes(x);
  uint32_t n = htonl(x);
  printf("0x%X 在網路中的傳輸順序:", x);
  printBytes(n);
}

how to IPC

  1. pipe(named pipe)
  2. popen
  3. message queue
  4. semophore
  5. signal
  6. shared memory
  7. socket
  8. file

deadlock condition

race condition: 輸出依據不受控制的事件出現順序或者出現時機 Critical section: access共享資源的code synchronization: 協調讓thread使用Critical section在時間上一致與統一 Cache coherence: 快取一致性 (為什麼放這邊?,可以看volatile)

  1. 資源唯一(互斥) => a node in a graph
  2. 不會被搶(不可搶占) => node cant be removed
  3. 拿著並等別的資源(占有且等待) => an edge to other node
  4. 別人也在等我(循環等待) => cycle

thread & process

  • process: OS的資源分配單位,彼此不影響
  • thread: 程式的執行單位,互相影響 a. a kernel thread to a user thread: linux’s clone(child process, LWP) b. a kernel thread to many user threads c. many kernel threads to many user threads

how to talk to kernel

  1. virtual filesystems(e.g. /proc, /sys, configfs, relayfs): kernel <-> user, exchange data
  2. netlink/UDP socket: kernel <-> user, exchange data
  3. mmap: kernel <-> user, exchange data
  4. syscall: user -> kernel, invoke function
  5. ioctl: kernel <-> user, exchange data
  6. signal from kernel: kernel -> user, invoke handler
  7. upcall: kernel -> user, invoke a function

ref

volatile

就是叫compiler每次都乖乖重新拿值,不然可能有神秘的優化把變數消滅掉

用處

  1. register
  2. 在multi-thread共用的變數
  3. 一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)

Can we use “const” and “volatile” in the same variable?

extern const volatile unsigned int rt_clock; 用在監看reg的值的時候

malloc、calloc、realloc

  • malloc: 就是allocate
  • calloc: allocate + 初始值
  • realloc: resize但是不一定從原本的位置開始,所以會copy舊資料到新位置

pointer size

看記憶體是幾位元的

  • 64: 8 bytes
  • 32: 4 bytes

OO in C

struct與透過method控制struct就是封裝 繼承要利用c的struct特性

因為struct只是去算field的offset,所以只要

  • 同一個位置
  • 同樣長度 就可以當成child class (484與golang很像)
struct A {
  int a;
};

struct B {
  int a;
  // extend
  int b;
};

void getA(A* this) {
  printf("%d\n", this->a);
}

void getB(B* this) {
  printf("%d %d\n", this->a, this->b);
}

int main() {
  B y{30,20};
  getA((A*)y);
}

那多形? struct自己存實作的fucntion ptr

macro

一些工具

  1. concat
#define cons(a,b) a##b
// cons(1,2) => 12
  1. 可變參數(預設參數),如果有尾巴的逗號會被吃掉 (C99)
#define foo(must, ...) _foo(must, (foo_args){.a = 8, .b = 3.14, __VA_ARGS__});
// foo('D', .b = 9, .a = 2);
  1. to_string
#define STR(s) #s
// STR(123) => "123"
  1. _Generic (C11, 就是泛型)
#define foo(a, b)                \
    _Generic((a),                \
        int: func1,               \
        double: _Generic((b),    \
                    int : func2,  \
                    double: func3 \
                )                \
    )(a, b)

使用時要注意

  1. 用括號
#define pow(a) (a) * (a)
// pow(1+2) => (1+2) * (1+2)
  1. 如果有temp變數要加大括號,不然會汙染到原本的scope
#define swap(a, b) { \
    int temp = a;    \
    a = b;           \
    b = temp;        \
}
  1. 幫數字標上type
#define NUM (100*123*234UL)
// 注意: 型別要放在數字後面!!
// U for unsigned
// L for long int or long float
// F for float
// default: double or int
  1. 如果要用macro在另一個macro要多一層讓macro先展開
#define A 2
#define CONS(a,b) a##b
// CONS(A,A) => AA
#define _CONS(a,b) a##b
#define CONS_GOOD _CONS(a,b)
// CONS_GOOD(A,A) => _CONS(2,2) => 22

useful macro & case study

#include <stdio.h>

#define debug(fmt, ...) { \
    fprintf(stderr, "(%s:%d) "fmt"\n", __FILE__, __LINE__, ##__VA_ARGS__); \
}

int main(void) {
    debug("%s %d", "Shit happen!", 1);

    return 0;
}
#define hash_hash # ## #
#define mkstr(a) # a
#define in_between(a) mkstr(a)
#define join(c, d) in_between(c hash_hash d)

//join(x, y)
//in_between(x hash_hash y)
//in_between(x ## y)
//mkstr(x ## y)

default args: 在函數做設定

#include <stdio.h>

#define f(...) def_f((f_args) {__VA_ARGS__})

typedef struct {
    int i;
    double j;
} f_args;

void real_f(int i, double j) {
    printf("%i %f\n",i,j);
}

void def_f(f_args args) {
    int i = args.i ? args.i : 10;
    double j = args.j ? args.j : 10.5;
    real_f(i,j);
}

int main() {
    f(3,8);
    f(.j=100.2, .i=4);
    f(2);
    f(.j=45.3);
    f();
    f(12,);
    return 0;
}

default args: 在struct(macro展開時)做設定

#include <stdio.h>

#define f(...) def_f((f_args){.i=10, .j=10.5, __VA_ARGS__})

typedef struct {
    int i;
    double j;
} f_args;

void real_f(int i, double j) {
    printf("%i %f\n",i,j);
}

void def_f(f_args x) {
    real_f(x.i,x.j);
}

int main() {
    //f(3,8);
    f(.j=100.2, .i=4);
    //f(2);
    f(.j=45.3);
    f();
    //f(12,);
    return 0;
}

floating point

  1. 不要直接比較,要變成看相對誤差
abs((0.1+0.2)-0.3) < epsilon
  1. 少用float做直接運算,轉成整數

  2. 使用另外設計的Lib

  3. 調整算式

豐富的case

ISR

__interrupt double compute_area(double radius) // 1. no args (這應該是上半部)
{
  double area = PI * radius * radius; // 2. kernel space 用float?!
  printf("\nArea = %f" area); // 3. 在中斷跑IO!?
  return area; // 4. irq沒有return,syscall有
}

pitfall

auto cast

有unsigned就會自動變成unsigned

void foo(void)
{
  unsigned int a = 6;
  int b = -20;
  (a+b > 6) ? puts("> 6") : puts("<= 6"); // > 6
}

type size

只有char確定是1byte!!

所以下面的code可能會出事,因為unsigned int不一定是4bytes

unsigned int zero = 0;
unsigned int compzero = 0xFFFF; /*1's complement of zero */
typesize
char1 byte
unsigned char1 byte
signed char1 byte
int2 or 4 bytes
unsigned int2 or 4 bytes
short2 bytes
unsigned short2 bytes
long8 bytes or (4bytes for 32 bit OS)
unsigned long8 bytes

p++ & ++p

就算有括號,p++的inc還是後做,++p的inc還是先做

int a[5]={1,2,3,4,5};
int *p=a;
*(p++)+=123;
*(++p)+=123;
//124 2 126 4 5

typedef

#define dPS struct s *

typedef struct s * tPS;


dPS p1,p2; // => struct s * p1,p2;

tPS p3,p4;// => struct s *p1,*p2;

pointer & array type

先看array,再看pointer,有括號就先看括號

int a[10]; // An array of 10 integers

int *a[10]; // An array of 10 pointers to integers
// int ((*a)[10])

int (*a)[10]; // A pointer to an array of 10 integers

int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer

int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer

const & pointer

int const *a; // ptr的內容固定
int * const a; // 被指到的int固定
const int const * a; // 兩個都固定

計組

Pipeline

  1. instruction fetch
  2. instruction decode/fetch register
  3. instruction execute/ branch or jump
  4. memory access
  • to mem
  1. write back
  • to reg

因為現在是所有指令共用datapath,所以要pipeline reg讓stage去load需要的資料去完成每個階段的任務

harzards

  • Data Hazard
    • LoadStore (RAW): 還沒寫完就被讀(拿到舊的值)
    • StoreStore (WAW): 前面還沒寫完,後面已經寫進去了
    • StoreLoad (WAR): 來沒讀完就被寫(拿到未來的值)
    • sol
      • compiler 或是 cpu的 stall
      • forwarding: 把需要的資訊丟到後面的pipeline reg,讓後面的stage先做事
  • Control Hazard
    • if 的 bool還沒被算出來!!
    • sol
      • stall
      • 分支預測(猜)
  • Structural hazard
    • 在pipeline上指令的需要同一個資源
    • sol
      • stall
      • 亂序執行

Network Programming

server

  • socket
    • new socket()
  • bind
    • 填ip, port
  • listen
    • socket.start()
  • accept (可以去看block-nonblock-sync-async補一下block與non-block的知識)
    • loop -> get socket
    • read / write
    • close

client

  • socket
  • connect
  • write / read
  • close

傳struct

不能直接傳,大小頭、padding、type的size不一定會一樣,會可能會轉不回來

  1. 轉成string
  2. 用htonl與ntohl,自己把int轉一轉
  • host order to network order long (integer, s是short integer)
  • network order to host order long (integer, s是short integer)

float就要自己設計格式,沒有htonl與ntohl可以用,不然就是去找序列化的lib,像protobuf

pipe

  • pipefs
  • create a file in pipefs, which is in mem
  • return 2 fd, one for writing, another for reading

how tcpdump works

  • 透過libpcap的api生bpf

除此之外