[C言語]メモリ管理

メモリ確保関数
malloc: 指定されたメモリを確保する、確保できない場合はNULLを返す
calloc: 指定されたサイズのメモリブロックを確保、確保した領域を0クリア
realloc: 確保済みのメモリを拡張

メモリ解放関数
free: malloc, calloc, reallocで確保した領域を解放する

#include <stdio.h>
#include <stdlib.h>

int main(void){

	// 100byteメモリ確保
	char *p = malloc(100);
	if(p == NULL){
	}

	// 100 * sizeof(int)分のメモリを確保し、中身を0でクリアする
	// p = malloc(100 * sizeof(int)); memset(p, 0, 100*sizeof(int));
	int *p2 = calloc(100, sizeof(int));
	if(p2 == NULL){

	}

	// 100byte確保しているメモリを200byteに拡張
	char *p3 =realloc(p, 200);
	if(p3 == NULL){

	} else {
		p = p3;
	}

	free(p2);
	free(p);


	return 0;
}

[C言語]カーネル

Unix系では新しいプロセスはfork()により生成されexec()系の関数により新たなプログラムに書き換わる
プログラムを実行する為のライブラリ関数としてexeclp()やexecvp()などがある
execveシステムコールが発行されるとカーネルの仕事となる
– 実行ファイルを読み込み、仮想メモリ上にマッピングする
– argc/argv[]、BSSの初期化、環境変数の引き渡しなどを行う
– 実行ファイル上のエントリポイントから実行を開始する

※カーネルを読み込むには5年程度の技術と時間がかかる
カーネルの大部分はC言語で書かれているが、CPU固有の処理部分はアセンブラ言語で書かれている

## カーネルのディレクトリ
Documentation ドキュメント
arch アーキテクチャ依存
block ブロック入出力層
crypto 暗号化
drivers デバイス・ドライバ
firmware ファームウェア
fs VFS(Virtual File System)とファイル・システム
include カーネル用のヘッダ・ファイル
init 起動と初期化(initialization)
ipc プロセス間通信(interprocess communication)
kernel カーネルの中心部分
lib ヘルパ
mm メモリ管理(memory management)
net ネットワーク
samples サンプルコード
scripts カーネルのコンパイルに必要なスクリプト
security Lnux Security Module
sound サウンド・サブシステム
tools カーネル開発用のツール
usr 起動直後にユーザ空間で実行されるプログラム(initramfs)
virt 仮想化インフラ

C言語ではcrt(C RunTime startup)というアセンブリで記述されたプログラムが一番最初に実行される
OSの開発ではこのcrtを環境に合わせて用意する

[C言語]メモリリーク

メモリリークとは、動的に割当てたメモリ領域を解放し忘れることで次第にメモリ資源を食い潰していき、いずれプログラムやシステムに異常をきたすという問題。
c言語のメモリ解放はmalloc()やfree()が使われてきた。
malloc()で割当てて、free()で解放する。
メモリの解放を忘れていると、プログラムが終了するまでメモリ資源を無駄に占有し続ける。

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

int maikFile(char *fileName, int size){
	FILE *fp;
	char *pData;

	// メモリ解放
	pData = malloc(size);
	if(pData == NULL){
		return 1;
	}

	fp = fopen(fileName, "wb");
	if(fp == NULL){
		return 1;
	}

	memset(pData, 0x20, size);
	fwrite(pData, size, 1, fp);
	fclose(fp);

	free(pData);
	return 0;
}


int main(int argc, char** argv[]){

	return 0;

}

ファイルシステムオープンの失敗を繰り返すと、メモリは解放されずにreturn1となるのでメモリリークになる。

[C言語]ファイヤーウォール

ファイヤーウォールとはパケットの制御または破棄を行うソフトウェアである

特定のパケットとは、
– 特定のポートにアクセスするパケット
– 特定のIPアドレスからのパケット
– 特定の内容をもつパケット
などがある。

ファイヤーウォールを作成するには
– パケットの中身を見る
– パケットの転送可否を行う

### パケットキャプチャ
パケットの中身を見る
TCPやUDPはもとより、IPやEthernetレベルのデータリンク情報も解析する
一般的に tcpdump や wireshark などがある

#include <stdio.h>
#include <net/if.h> // ネットワークインターフェイス構造体 PF_PACKET
#include <net/ethernet.h> // ETH_P_ALL

void analyzePacket(u_char *buf){
	printEtherHeader(buf);
}

void printEtherHeader(u_char *buf){
	struct ether_header *eth;
	eth = (struct ether_header *)buf;
	printf("Dst MAC addr :%17s\n", mac_ntoa(eth->ether_dhost));
	printf("Src MAC addr :%17s\n", mac_ntoa(eth->ether_shost));
	printf("Ethernet Type :0x%04x\n", ntohs(ether_type));
}

char *mac_ntoa(u_char *d){
	static char str[18];
	sprintf(str,"%02x:%02x:%02x:%02x:%02x:%02x",d[0],d[1],d[2],d[3],d[4],d[5]);
	return str;
}

int main(int argc, char** argv[]){

	int soc;
	u_char buf[65535]; // u_charはunsigned char
	soc = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
	while(1){
		read(soc,buf,sizeof(buf));
		analyzePacket(buf);
	}
	return 0;

}

int socket(int domain, int type, int protocol);
PF_PACKETを指定すると,データリンク層やネットワーク層の生データを扱える
通常はSOCK_STREAM(TCPの場合)やSOCK_DGRAM(UDPの場合)
ETHERNETとは、TCP/IPプロトコルのネットワークインターフェース層に対応する有線の規格

宛先MAC address(固有識別番号)、送信元MAC address, type, ip以降
ビッグエンディアン

[C言語]ICMP

ICMPとはInternet Control Message Protocol
Internet Protocolのデータグラム処理における誤りの通知や通信に関する情報の通知などのために使用される

#include <stdio.h>

#include <sys/socket.h> // Internet Protocol family
#include <netinet/in.h> // Internet Protocol family

#include <netinet/ip.h>  // IPヘッダ構造体
#include <netinet/ip_icmp.h> // ICMPヘッダ

unsigned short checksum(unsigned short *buf, int bufsz){

	unsigned long sum = 0;

	while(bufsz > 1){
		sum += *buf;
		buf++;
		bufsz -= 2;
	}

	if(bufsz == 1){
		sum += *(unsigned char *)buf;
	}

	sum = (sum & 0xffff) + (sum >> 16);
	sum = (sum & 0xffff) + (sum >> 16);

	return ~sum;
}

int main(int argc, char** argv[]){

	int sock;
	struct icmphdr hdr;
	struct sockaddr_in addr;
	int n;

	char buf[2000];
	struct icmphdr *icmphdrptr;
	struct iphdr *iphdrptr;

	if(argc != 2){
		printf("usage : %s IPADDR\n", argv[0]);
		return 1;
	}

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(argv[1]);

	sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if(sock < 0){
		perror("socket");
		return 1;
	}

	memset(&hdr, 0, sizeof(hdr));

	hdr.type = ICMP_ECHO;
	hdr.code = 0;
	hdr.checksum = 0;
	hdr.un.echo.id = 0;
	hdr.un.echo.sequence = 0;

	hdr.checksum = chechsum((unsigned short *)&hdr, sizeof(hdr));

	n = sendto(sock, (char *)&hdr, sizeof(hdr), 0, (struct sockaddr *)&addr, sizeof(addr));
	if (n < 1){
		perror("sendto");
	}

	memset(buf, 0, sizeof(buf));

	n = recv(sock, buf, sizeof(buf), 0);
	if(n < 1){
		perror("recv");
	}

	iphdrptr = (struct iphdr *) buf;

	icmphdrptr = (struct icmphdr *)(buf + (iphdrptr->ihl * 4));

	if(icmphdrptr->type == ICMP_ECHOREPLY){
		printf("received ICMP ECHO REPLY\n");
	} else {
		printf("received ICMP %d\n", icmphdrptr->type);
	}

	close(sock);
	

	return 0;

}

[C言語]UDP

UDPはデータが宛先に届いたかどうかを関知しない為、到着を保証しない
複数の相手に同時にデータを送信できる、TCPは1対1のユニキャストのみ
TCPよりリアルタイム性が高い
用途は映像、音楽のストリーミング、Voice Over IPなど

### UDP受信
– ソケットを作る
– bindするIPアドレスとポートを設定
– ソケットに名前をつける
– データを受け取る

#include <stdio.h>
#include <sys/types.h> // typedef シンボルおよび構造体のコレクション
#include <sys/socket.h> // Internet Protocol family
#include <netinet/in.h> // Internet Protocol family

int main(int argc, char** argv){

	int sock;
	struct sockaddr_in addr; // sockaddr_inはIPアドレスやポート番号を保持する為の構造体

	char buf[2048];

	sock = socket(AF_INET, SOCK_DGRAM, 0); // ソケットの作成

	addr.sin_family = AF_INET; // アドレスファミリ
	addr.sin_port = htons(12345); // ポート番号、 htonsはネットワークバイトオーダに変換
	addr.sin_addr.s_addr = INADDR_ANY; // IPアドレス

	bind(sock, (struct sockaddr *)&addr, sizeof(addr)); // sockはソケットとアドレスの結合

	memset(buf, 0, sizeof(buf)); // バイトメモリブロックのセット
	recv(sock, buf, sizeof(buf), 0); // ソケットからメッセージを受け取る

	printf("%s\n", buf);

	close(sock);

	return 0;

}

### UDP送信
– ソケットを作る
– 宛先を指定して送信

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(){
	
	int sock;
	struct sockaddr_in addr;

	sock = socket(AF_INET, SOCK_DGRAM, 0);

	addr.sin_family = AF_INET;
	addr.sin_port = htons(12345);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");

	sendto(sock, "HELLO", 5, 0, (struct sockaddr *)&addr, sizeof(addr));

	close(sock);

	return 0;
}

TCP/IPはデータを切れ目のないストリームとして扱う、ストリームのイメージは固定された通信路
一方、UDPはデータグラム(塊)として扱う

TCPとUDPについてわかってきました。

[C言語]TCP/IP

サーバ側

#include 
#include 
#include 
#include 

int main(int argc, char** argv){
	int sd;
	int acc_sd;
	struct sockaddr_in addr;

	socklen_t sin_size = sizeof(struct sockaddr_in);
	struct sock_in from addr;

	char buf[2048];

	memset(buf, 0, sizeof(buf));

	if((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		perror("socket");
		return -1;
	}

	addr.sin_family = AF_INET;
	addr.sin_port = htons(22222);
	addr.sin_addr.s_addr = INADDR_ANY;

	if(bind(sd, (struct sockaddr *)&addr, sizeof(addr)) < 0){
		perror("bind");
		return -1;
	}

	if(listen(sd, 10) < 0){
		perror("listen");
		return -1;
	}

	if((acc_sd) = accept(sd, (struct sockaddr *)&from_addr, &sin_size)) < 0 {
		perror("accept");
		return -1;
	}

	if(recv(acc_sc, buf, sizeof(buf), 0) < 0){
		perror("recv");
		return -1;
	}

	close(acc_sd);

	close(sd);

	printf("%s\n", buf);

	return 0;

}

client

#include 
#include 
#include 

int main(int argc, char** argv){
	int sd;
	struct sockaddr_in addr;

	if((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		perror("socket");
		return -1;
	}

	addr.sin_family = AF_INET;
	addr.sin_port = htons(22222);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");

	connect(sd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));

	if(send(sd, "sending process", 17, 0) < 0){
		perror("send");
		return -1;
	}

	close(sd);

	return 0;
}

[C言語]バイナリファイル

パケットフィルタリングとは、通信機器やコンピュータの持つネットワーク制御機能の一つで、外部から受信したデータを管理者が設定した一定の基準に従って通したり破棄したりすること
Berkeley Packet Filterとは、ユーザランドからのコードをカーネル内で安全に実行する為の仕組み

$ grep BPF /boot/config-`uname -r`
CONFIG_CGROUP_BPF=y
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_NETFILTER_XT_MATCH_BPF=m
CONFIG_NET_CLS_BPF=m
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_JIT=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_TEST_BPF=m

#include 
#include 
#include 
#include 
#include "bpf_load.h"
#include "sock_example.h"
#include 
#include 

int main(int ac, char **argv){

	char filename[256];
	FILE *f;
	int i, sock;

	snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);

	if(load_bpf_file(filename)){
		printf("%s", bpf_log_buf);
		return 1;
	}

	sock = open_raw_sock("lo");

	assert(setsockopt(sock, SQL_SOCKET, SO_ATTACH_BPF, prog_fd, sizeof(prog_fd[0])) == 0);

	f = popen("ping -c5 localhost", "r");
	(void) f;

	for(i = 0; i < 5; i++){
		long long tcp_cnt, udp_cnt, icmp_cnt;
		int key;

		key = IPPROTO_TCP;
		assert(bpf_map_lookup_elem(map_fd[0], &key, &tcp_cnt) == 0);

		key = IPPROTO_UDP;
		assert(bpf_map_lookup_elem(map_fd[0], &key, &udp_cnt) == 0);

		key = IPPROTO_ICMP;
		assert(bpf_map_lookup_elem(map_fd[0], &key, &icmp_cnt) == 0);

		printf("TCP %lld UDP %lld ICMP %lld bytes\n", tcp_cnt, udp_cnt, icmp_cnt);
		sleep(1);
	}

	return 0;
}

TCPとはIPと同様にインターネットにおいて標準的に利用されている プロトコル。TCPは、IPの上位プロトコルでトランスポート層で動作する。
ネットワーク層のIPとセッション層以上のプロトコル(HTTP、FTP、Telnetなど)の橋渡しをする

UDPはUser Datagram Protocolの略、アプリケーション同士が最小限の仕組みでデータを送受信するプロトコル、リアルタイム性が重要視される通信で使われる、送信者アドレスの偽装が容易であるためDNSなどを使ったDDoS攻撃で使われることも多い

ICMPはIPプロトコルの「エラー通知」や「制御メッセージ」を転送するためのプロトコル、TCP/IPが実装されたコンピュータ間で通信状態を確認するために使用

[C言語]バイナリファイル

0と1のビットの並びで表現されたファイルをバイナリファイルと呼ぶ。また、そのデータをバイナリデータと呼ぶ。
行を変えるという概念がない。

– エンディアン
2バイト以上あるデータをメモリ上に配置する時に、どのように並べるかというルールのことで、リトルエンディアンとビッグエンディアンがある。
「0x00 0x00 0x003 0x84」(ビッグエンディアン)と「0x84 0x003 0x00 0x00」(リトルエンディアン)
IntelのCPUはリトルエンディアン

#include <stdio.h>

int main(void){
	printf("%X\n", 'x');
	printf("%X\n", 'y');
	printf("%X\n", 'z');
}

– 読み書き
fopen関数に”b”を含む


#include
#include

int main(void){
FILE *fp = fopen(“test.bin”,”rb”);
if(fp == NULL){
fputs(“ファイルオープンに失敗しました。\n”, stderr);
exit(EXIT_FAILURE);
}

int num = 900;
double d = 7.85;
char str[] = “xyzxyz”;

if(fwrite(&num, sizeof(num), 1, fp) < 1){ fputs("1.ファイル書き込みに失敗しました。\n", stderr); exit(EXIT_FAILURE); } if(fwrite(&d, sizeof(d), 1, fp) < 1){ fputs("2.ファイル書き込みに失敗しました。\n", stderr); exit(EXIT_FAILURE); } if(fwrite(str, sizeof(str[0]),sizeof(str),fp) < sizeof(str)){ fputs("3.ファイル書き込みに失敗しました。\n", stderr); exit(EXIT_FAILURE); } if(fclose(fp) == EOF){ fputs("ファイルクローズに失敗しました。\n", stderr); exit(EXIT_FAILURE); } return 0; } [/code] $ ./test 1.ファイル書き込みに失敗しました。

[C言語]データベース

### 作り方
データを構造体配列にして検索で工夫する
データのレコードやデータベースの項目情報は動的メモリ確保されたメモリ領域にリスト構造で保持
そこから検索アルゴリズムでデータを取り出し、必要に応じてソート処理
データの保存・読み込みにはバイナリファイルの取り扱いノウハウが必要

### 必要知識
検索、リスト構造、ソート、ハッシュ、動的メモリ確保、バイナリファイルの取り扱い

### 構造体配列のサンプル

#include <stdio.h>

struct pop_dt {
	char region[20];
	long pop;
};

int main(void){

	struct pop_dt world[7] = {
		{"asia", 3769},
		{"north_america", 498},
		{"south_america", 357},
		{"europe", 725},
		{"africa", 832},
		{"oceania", 31},
		{"nowhere", 0}
	};

	int i;
	for(i=0; world[i].pop != 0; i++){
		printf("%-15s %6d\n", (world+i)->region, (world+i)->pop);
	}

	return 0;
}

$ ./main
asia 3769
north_america 498
south_america 357
europe 725
africa 832
oceania 31

一つの事をやろうとしたら、副次的に色々な事を学ばなければならない。その量がやたら多いように感じるのは実力がないからか。