Linux系统概述

Linux文件与权限

linux目录说明

/boot: 存放的启动linux时使用的内核文件,包括链接文件及镜像文件

/etc : 存放所有系统需要的配置文件和子目录列表,更改目录下的文件可能会导致系统无法启动

/lib : 存放基本代码库(比如c++库),其作用类似于Windows下的DLL文件,几乎所有的应用程序都要用到这些分享库

/bin : 存放着一系列的普通命令

/sbin:存放着只有管理员才能使用的命令

/dev: Device(设备)的缩写,存放的时Linux的外部设备,在Linux中访问设备和访问 文件是一样的

/media:类Windows下的其它设备,如Upan光驱等

/mnt:用于临时挂在其它文件系统

/run:是一个临时文件系统,存储系统启动以来的信息,当系统重启时,这个目录下的文件应该被清除

/lost+found:一般情况下为空,用于存放系统非法关机后的产生的文件

/tmp:用于存放临时文件

/root: 系统管理员用户家目录

/home:除管理员以外的用户家目录所处的目录

/usr: 用户的应用程序和文件基本都放在这个目录下

/usr/bin:系统用户使用的应用程序和指令

/usr/sbin:超级用户使用的程序

/usr/src:内核源代码默认存放的位置

/usr/share:apt-get下载的默认目录

/var:存放经常需要更改的数据(/var/log)

/proc:管理内存空间,是系统内存映射的虚拟目录,可以直接访问这个目录来获取系统信息

/etc目录下的文件

文件 描述
/ect/host 网址域名与其对应的IP地址建立一个关联的数据库 通常在网络验证时可以使用,让指定的域名指向本地
/etc/passwd 账号信息文件 用户名:密码:UID:GID:描述信息:家目录:默认shell
/etc/shadow 密码文件 用户名:加密密码:最后一次修改时间:最小修改时间间隔:密码有效期:密码过期前的警告天数,密码过期后的宽限时间:账号失效时间:保留
/etc/group 用户组配置文件 组名:密码:GID:用户列表
/etc/gshadow 组用户的密码信息,通常没有设置 组名:加密密码:组管理员:组附加用户列表
/etc/hostname 主机名称 hostnamectl set-hostname 主机名
/etc/profile 环境变量配置文件 系统环境b变量
/etc/inittab 系统运行级别
/etc/init.d 通常用户管理某个服务 /etc/init.d/networking start 启动网卡
/etc/rc.local 开机后会执行这个文件 遍历/etc/profile.d/下的脚本执行
/etc/profile.d 开机后会这些这个目录下的sh脚本
etc/crontab 计划任务文件 运行crontab就会创建
/etc/cron.d 计划任务目录 可以添加任务sh文件,可是:分 时 日 月 周 命令

/proc目录下的文件

proc的由来:

linux内核是一个非常庞大、非常复杂的一个单独的程序,对于这样的一个程序来说调试是非常复杂的。像 kernel这样庞大的项目,给里面添加更改一个功能是非常麻烦的,因为你这添加的一个功能可能会影响其他已经有的。

早期内核版本中尽管调试很麻烦,但是高手们还可以凭借个人超凡脱俗的能力去驾驭但是到了2.4左右的版本的时候,这个难度已经非常大了。

为了降低内核调试和学习的难度,内核开发者们在内核中添加了一些属性专门用于调试内核,proc文件系统就是一个尝试。

proc文件系统的思路是:在内核中构建一个虚拟文件系统/proc,内核运行时将内核中一些关键的数据结构以文件的方式呈现在/roc目录中的一些特定文件中,这样相当于将不可见的内核中的数据结构以可视化的方式呈现给内核的开发者。

proc文件系统给了开发者一种调试内核的方法:我们通过实时的观察proc/xxx文件,来观看内核中特定数据结构的值。在我们添加一个新功能的前后来对比,就可以知道这个新功能产生的影响对还是不对。

proc目录下的文件大小都是0,因为这些文件本身并不存在于硬盘中,他也不是一个真实文件,他只是一个接口,当我们去读取这个文件时,其实内核并不是去硬盘上找这个文件,而是映射为内核内部一个数据结构被读取并且格式化成字符串返回给我们。所以尽管我们看到的还是一个文件内容字符串,和普通文件一样的;但是实际上我们知道这个内容是实时的从内核中数据结构来的,而不是硬盘中来的。

文件 描述
/proc/cpuinfo 处理器信息相关的文件
/proc/devices 系统已经加载的所有块设备和字符设备的信息
/proc/interrupts x86/x64体系架构系统上每个IRQ相关的中断号列表
/proc/meminfo 系统中关于当前内存的利用状况等信息
/proc/iomem 每个物理设备上的记忆体(RAM/ROM)在系统中映射信息
/proc/version 当前系统运行的内核版本
/proc/N PID为N的进程
/proc/N/cmdline 用于查看某个进程启动命令行
/proc/N/environ 该程序运行时使用的环境变量
/proc/N/fd 该程序使用的所有文件描述符
/proc/N/maps 每个可执行文件在内存中映射区域及访问权限所组成的列表
/proc/N/mem 进程所占内存,不可以直接读
/proc/N/status 与stat提供的信息类似,可读性较好
/proc/N/stat 进程当前的状态信息,可读性差

VSCODE 开发环境

VSCODE介绍

vscode是微软开发的一款轻量级的编辑器,满足现在绝大部分编程语言的开发,只要安装相应的插件,并且他是支持WIndows和Linux。

Vistual Studio Code作为广受好评的开发工具,已经被越来越多的开发着当作首选的开发工具

Unbutu安装vscode

  1. 通过snap命令安装sudo snap install –classic code # or code-insiders
  2. 通过下载deb包安装apt-get install xxx.deb 缺少依赖可以执行sudo apt-get install -f

VSCODE插件

中文插件 搜所 “chinese”

c/c++运行环境 搜索 “c/c”

扩展图标 搜索 “icons”

Linux C编译器与调试器

调试与生成

编写一行代码

1
2
3
4
5
6
#include <stdio.h>
int main()
{
printf("Hello World");
return 0;
}

生成配置文件

无论是Linux还是Windows,用户配置文件都放在.vscode下

用户配置是针对某一个工程或文件夹而特别做到,所有配置都放在该文件下的.vscode隐藏文件夹中

配置文件launch.json

注意点击main.c文件后再添加配置,否则没有提示GCC配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"version": "0.2.0",
"configurations":[
{
"name": "gcc", //配置名称,将会在启动配置的下拉菜单中显示
"type":"cppdbg", //配置类型,这里只能为cppdbg
"request": "launch", //请求配置类型,可以为 launch(启动)或 attach(附加) "program":"${fileDirname}/${fileBasenameNoExtension}",//将要调试的程序路径
"args":[], //程序调试时传递给程序的令行参数,一般设为空即程序调试时传递给程序的命
“stopAtEntry": false,//设为true时程序将暂停在程序入口处,一般设置为true
"cwd": "${workspaceFolder}",//调试程序时的工作目录
"environment":[], //(环境变量)
"externalConsole":true,//调试时是否显示控制台窗口,一般设置为true显示控制台
"internalConsoleOptions": "neverOpen",//如果不设为 neverOpen,调试时会跳到“调试控制台”选项卡,你应该不需要对gdb手动输命令吧?
"MIMode": "gdb", //指定连接的调试器,可以为gdb或1ldb。但目前1db在 windows下没有预编译好的版本。
"miDebuggerPath": "gdb.exe",//调试器路径, Windows下后缀不能省略, Linux下则去掉
"preLaunchTask": "build" //调试会话开始前执行的任务,一般为编译程序。与tasks.json的 label相对应
}
]
}

配置文件Tasks.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"tasks": [
{
"type": "cppbuild", //脚本类型
"label": "C/C++: gcc", //任务名
"command": "/usr/bin/gcc", //编译命令
"args": [ //编译的参数 gcc -g file -o xxx
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"//生成的文件名
],
"options": { //编译时运行目录
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "调试器生成的任务。"
}
],
"version": "2.0.0"
}

Linux编译器与库

GCC介绍

一、什么是Gcc

Linux系统下的Gcc(GNU C Compiler)是GNU推出的功能强大、性能优越的多平台编译器,是GNU的代表作品之一。gcc是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%。

Gcc编译器能将C、C++语言源程序、汇程式化序和目标程序编译、连接成可执行文件,如果没有给出可执行文件的名字,gcc将生成一个名为a.out的文件。在Linux系统中,可执行文件没有统一的后缀,系统从文件的属性来区分可执行文件和不可执行文件。

二、gcc所遵循的部分约定规则

前面提到编译的后缀问题,而gcc则通过后缀来区别输入文件的类别,下面我们来介绍gcc所遵循的部分约定规则。

.c为后缀的文件,C语言源代码文件;

.a为后缀的文件,是由目标文件构成的档案库文件;
.C或.cc或.cxx为后缀的文件,是C++源代码文件;

.h为后缀的文件,是程序所包含的头文件;

.i为后缀的文件,是已经预处理过的C源代码文件;

.ii为后缀的文件,是已经预处理过的C++源代码文件;

.m为后缀的文件,是Objective-C源代码文件;

.o为后缀的文件,是编译后的目标文件;

.s为后缀的文件,是汇编语言源代码文件;

.S为后缀的文件,是经过预编译的汇编语言源代码文件。

三、Gcc的执行过程

虽然我们称Gcc是C语言的编译器,但使用gcc由C语言源代码文件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤∶预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编(Assembly)和连接(Linking)。

  1. 命令gcc首先调用cpp进行预处理,在预处理过程中,对源代码文件中的文件包含(include)、预编译语句(如宏定义define等)进行分析。

    gcc mian.c -E -o main.i

  2. 接着调用cc1进行编译,这个阶段根据输入文件生成以.o为后缀的目标文件。汇编过程是针对汇编语言的步骤,调用as进行工作,一般来讲,.S为后缀的汇编语言源代码文件和汇编

    gcc main.i -S -o main.S

  3. s为后缀的汇编语言文件经过预编译和汇编之后都生成以.o为后缀的目标文件。

    gcc main.S -c -o main.o

  4. 当所有的目标文件都生成之后,gcc就调用ld来完成最后的关键性工作,这个阶段就是连接。在连接阶段,所有的目标文件被安排在可执行程序中的恰当的位置,同时,该程序所调用到的库函数也从各自所在的档案库中连到合适的地方。

    gcc main.o -o main

四、Gcc的基本用法和选项

在使用Gcc编译器的时候,我们必须给出一系列必要的调用参数和文件名称。Gcc编译器的调用参数大约有100多个,其中多数参数我们可能根本就用不到,这里只介绍其中最基本、最常用的参数

Gcc最基本的用法是∶gcc[options] [filenames] ,其中options就是编译器所需要的参数,filenames给出相关的文件名称。

五、Gcc的参数选项

-c,只编译,不连接成为可执行文件,编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件,通常用于编译不包含主程序的子程序文件。

-o output_filename,确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的可执行文件a.out。

-g,产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项。

-O,对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是,编译、连接的速度就相应地要慢一些。

-O2,比-O更好的优化编译、连接,当然整个编译、连接过程会更慢。
-Idirname,将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数。

静态库

之所以成为【静态库】,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库起链接打包到可执行文件中。因此对应的链接方式称为静态链接。

静态库优点,静态库对函数库的链接是放在编译时期完成的,程序在运行时与函数库再无瓜葛,移植方便。缺点浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件

  1. 将所有要加入静态库的文件编译成obj文件

    gcc -c fun.c -o fun.o

  2. 将.o文件打包生成静态库

    ar -crs libfun.a fun.o

  3. 编译到main主文件中

    gcc main.c -L ./ -lfun -o app

动态库

动态库是为了解决静态库的缺点实现的

动态库再程序编译时并不会链接到目标代码中,而是在程序运行时才被载入,不同的应用程序如果需要调用相同的库,那么在内存里只需要一份该共享库的实例,规避了浪费空间问题。动态库在运行程序时才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦,用户只需要更新动态库即可

①生成动态库有两种方式及

  1. gcc -fPIC -shared fun.c -o libfun.so

  2. gcc -fPIC -c fun.c -o fun.o

    gcc -shared fun.o -o libfun.so

②生成可执行代码

gcc -g main.o libfun.so -o app

③将动态库安装到系统目录或设置环境变量,否则程序无法运行

  1. cp libfun.so /usr/lib/
  2. exprot LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)

Linux编程

Linux文件编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>


//写文件
void WriteFile(char* Name)
{
//创建文件
int fd = open(Name, O_RDWR | O_CREAT);
if(fd == -1)
{
printf("open error:%s\n", strerror(errno));
return;
}
//写文件
char buf[] = {"写入文件的内容\n"};
int ByteNum = write(fd, buf, strlen(buf));
if(ByteNum > 0)
{
printf("写入成功\n");
}
else
{
printf("写入失败\n");
}
//关闭文件
close(fd);
}
//读文件
void ReadFile(char* Name)
{
int fd = open(Name, O_RDONLY);
if(fd == -1)
{
printf("open error:%s\n", strerror(errno));
return;
}
//读取文件信息
struct stat fileinfo;
fstat(fd,&fileinfo);
char* Buffer = (char*)malloc(fileinfo.st_size);
int ByteNum = read(fd ,Buffer, fileinfo.st_size);
if(ByteNum > 0)
{
printf("读取成功\n");
}
printf("%s\n", Buffer);
free(Buffer);
close(fd);
}

int main()
{
//创建并写入
WriteFile("WriteFile.txt");
//读取文件
ReadFile("WriteFile.txt");
return 0;
}

Linux进程编程

环境变量操作

相关API

函数 描述
getenv 获取指定的环境变量
setenv 设置环境变量
unsetenv 取消指定的环境变量
putenv 设置环境变量

进程操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

//遍历进程
void EnumProcess()
{
// 1. 打开目录
struct DIR *dir = opendir("/proc");
// 2. 遍历目录流信息
struct dirent * ent;
int pid;
while(ent = readdir(dir))
{
// 3.遍历进程
if(ent->d_name[0] >='0' && ent->d_name[0]<='9' )
{
//获取进程pid
sscanf(ent->d_name,"%d",&pid);
char allpath[100];
sprintf(allpath,"/proc/%d/status",pid);
// 打开进程信息
int fd = open(allpath,O_RDONLY);
// 获取第一行数据
char buf[1024]={};
read(fd,buf,1024);
// 关闭描述符
close(fd);
// 提取进程名称
char processName[100];
sscanf(buf,"Name: %s",processName);
printf("PID:%d name:%s \n",pid,processName);
}
}
// 3.关闭目录
closedir(dir);
}

pid_t CreatProcess(char*path)
{
pid_t pid = fork();
if(pid==0) // 子进程
{
execl(path,NULL);
printf("child %d\n",getpid());
}else if (pid <0)
{
printf("创建错误\n");
}
return pid;
}
void WaitProcess()
{
pid_t pid;
pid = fork();
if(pid == 0)//子进程
{
printf("我是子进程 %d \n",getpid());
sleep(3);
}else if (pid > 0) // 父进程
{
// 想要同步
//wait(NULL);
int status;
wait(&status);
printf("我是父进程 %d\n",getpid());
}else{
printf("创建失败 %d\n");
}
}
int main()
{
//遍历进程
EnumProcess();
//创建进程
CreatProcess("/home/pb/010editor/010editor");
//等待进程
WaitProcess();
return 0;
}

Linux线程编程

tasks.json配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: gcc 生成活动文件",
"command": "/usr/bin/gcc",
"args": [
"-g",
"${file}",
"-lpthread",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "调试器生成的任务。"
}
],
"version": "2.0.0"
}

线程创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void *thread_proc(void * arg)
{
for (int i = 0; i < 100; i++)
{
printf("[%d] child %08x \n",i,pthread_self());
}
// 回收资源
pthread_detach(pthread_self());
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thread_proc,NULL);
// 等待线程结束-回收资源
pthread_join(tid,NULL);

}

线程同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

// 信号量
sem_t g_sem;

// 全局变量(共享资源)
int g_source = 0;
// 线程1
void *workthread1(void * param){
// 循环增加10000次
for (size_t i = 0; i < 1000000; i++){
sem_wait(&g_sem);
g_source += 1;
sem_post(&g_sem);

}

return NULL;
}
// 线程2
void *workthread2(void * param){
// 循环增加10000次
for (size_t i = 0; i < 1000000; i++){
sem_wait(&g_sem);
g_source += 1;
sem_post(&g_sem);

}

return NULL;
}


int main(){

// 初始化信号量
sem_init(
&g_sem, // 信号量
0, // 进程中使用
1 // 资源可以数
);

pthread_t threads[2]={};
// 创建两个线程
pthread_create(&threads[0],NULL,workthread1,0);
pthread_create(&threads[1],NULL,workthread2,0);
// 等待线程销毁
for (size_t i = 0; i < 2; i++)
{
pthread_join(threads[i],NULL);
}
// 显示全局变量的内容
printf("g_source = %d \n",g_source);

// 销毁信号
sem_destroy(&g_sem);

return 0;
}

Linux网络编程

不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame)。数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理

为了方便应用层对网络通讯,由加利福尼亚大学 Berkeley分校开发了一套网络通讯接口(SOCKET),最初是为UNIX操作系统开发的,后来随着uniⅸ操作系统的广泛使用,套接字成为当前最流行的网络通信应用程序接口之一。

Linux下的网络编程与 Windows下非常类似,使用也是 socket编程(windows抄袭的 Linux).

相关头文件

1
2
3
4
5
#include<sys/types.h>/类型定义
#include<sys/socket.h>提供网络通信函数
#include<unistd. h>针对系统调用的封装fork, pipe read write等
#include<netinet/in.h>互联网地址族定义数据结构 sockaddr_in
#include<arpa/inet.h>提供IP地址转换函数

相关API

函数 描述
int socket(int domain, int type, int protocol); 创建 socket对象
int bind(int sockfd, const struct sockaddr *addrsocklen_t addrlen); 绑定端口
int listen(int sockfd, int backlog); 监听端口
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 连接主机
int accept(int sockfd, struct sockaddr *, socklen_t *addrlen); 接受连接
ssize_t read(int fd, void *buf, size_t count); 接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags): 接收数据
ssize_t write(int fd, const void *buf, size_t count); 发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags); 发送数据
int close(int fd); 关闭 socket对象
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); UDP发送数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src addr, socklen_t *addrlen); UDP接收数据
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); 发送数据
ssize_t recvmsg (int sockfd, struct msghdr *msg, int fags); 接收消息

TcpServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <sys/types.h>   // 类型定义
#include <sys/socket.h> // 提供网络通信函数
#include<unistd.h> // 针对系统调用的封装 fork,pipe read write 等
#include<netinet/in.h> // 互联网地址族 定义数据结构sockaddr_in
#include<arpa/inet.h> // 提供IP地址转换函数
#include <stdio.h>

int main()
{
//1.创建socket
int server_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

//2.绑定
struct sockaddr_in addr;
addr.sin_port = htons(1234);
addr.sin_family =AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bind(server_sock,(struct sockaddr *)&addr,sizeof(addr));

//3.监听
listen(server_sock,SOMAXCONN);
printf("listen......\n");

//4.等待链接
struct sockaddr_in clinet_addr={};
int addr_len =sizeof(clinet_addr);

int client_sock =
accept(server_sock,(struct sockaddr *)&clinet_addr,&addr_len);

printf("connect .... \n");
//5.收发数据
send(client_sock,"hello",6,0);
printf("send msg to clinet .... \n");

char buf[1024]={};
recv(client_sock,buf,1024,0);
printf("recv client: %s \n",buf);

//6.关闭socket
close(client_sock);
close(server_sock);
}

TcpClien

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <sys/types.h>   // 类型定义
#include <sys/socket.h> // 提供网络通信函数
#include<unistd.h> // 针对系统调用的封装 fork,pipe read write 等
#include<netinet/in.h> // 互联网地址族 定义数据结构sockaddr_in
#include<arpa/inet.h> // 提供IP地址转换函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
//1.创建socket
int sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

//4.发起链接
struct sockaddr_in addr;
addr.sin_port = htons(1234);
addr.sin_family =AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
connect(sock,(struct sockaddr*)&addr,sizeof(addr));
printf("connect server.....\n");


//3.收发数据
char buf[1024]={};
recv(sock,buf,1024,0);
printf("recv server msg: %s \n",buf);

printf("input send msg:");
scanf("%s",buf);

send(sock,buf,strlen(buf)+1,0);
printf("send msg to server .... \n");

//6.关闭socket
close(sock);
}

Linux内存管理

内存相关API

函数 描述
int brk(void* addr); 分配堆空间(linux系统API)
void *sbrk(int ptr_t incrememt); 获取系统维护堆指针
void *malloc(size_t size); c语言分配堆内存
void free(void *ptr); c语言释放堆内存
void mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offser); 分配虚拟内存空间(liunx系统API)
int munmap(void *addr, size_t length); 释放虚拟内存空间
int mprotect(const void *start, size_t length, int prot); 修改指定内存属性

分配空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <sys/mman.h>
#include <stdio.h>
#include <error.h>
#include <string.h>
int main() {
char* p;
// 获取堆地址,第一次调用会分配一个地址
void *heap1 = sbrk(0);
// 分配堆空间
brk(heap1 + 1);
// 使用空间
p = (char*)heap1;
p[0] ='a';
//分配更多空间
brk(heap1 + 100);
strcpy(heap1, "hello");
printf(heap1);
fflush(stdout);

// 释放后,不能操作
brk(heap1);
return 0;
}

虚拟内存分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>


int main()
{
void *padd;
//1.分配虚拟空间
padd = mmap(
0, //起始地址
4096, //长度
PROT_READ|PROT_WRITE,//保护属性
MAP_ANONYMOUS|MAP_PRIVATE, // 匿名私有
0, //文件描述符
0 //偏移
);

//使用空间
char* pbuf = (char*)padd;

strcpy(pbuf,"hello");
printf("%s\n",pbuf);

// 释放空间
munmap(padd,4096);

}

修改内存保护属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>


int main()
{
char * pName = "hello world";
// 修改内存属性
mprotect(
((__int64_t)pName)/4096*4096, // 修改的地址是分页起始地址
4096, // 必须是分页大小
PROT_READ|PROT_WRITE|PROT_EXEC // 内存属性
);

pName[0] = '1';
printf("%s\n",pName);
}

Linx信号编程

发送信号相关API

函数 描述
int kill(pid_t pid, int signo); 给进发送一个信号
int raise(int signo); 给当前进程发送一个信号
void abort(void); 是当前进程接收到SIGABRT信号而异常终止
usingned int alarm(usingned int seconds); 设定一个闹钟,告诉内核多少秒后给当前进程发送SIGALRM信消息,默认终止当前进程

信号发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
int main(void)
{
//获取当前pid
pid_t pid = getpid();
printf("kill SIGTSTP\n");
//给pid发送一个挂起信号
kill(pid,SIGTSTP);

printf("raise SIGTSTP\n");
//给当前进程发送一个挂起信号
raise(SIGTSTP);

printf("alarm(1) SIGALARM\n");
//定时一秒钟后给自己发送SIGALARM信号,默认终止当前进程
alarm(1);
for (int i = 0;; i++){
printf("%d \n",i);
}
// 终止当前进程
abort();
printf("run end \n");
return 0;
}

信号闹钟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
// 信号处理函数
void alrmhandler(int sig){
printf("产生一个信号\n");
// 恢复默认行为
signal(SIGALRM,SIG_DFL);
// 忽略信号
//signal(SIGALRM,SIG_IGN);
}

int main()
{
printf("pid:%d",getpid());
// 注册信号
signal(SIGALRM,alrmhandler);
// 设置一个闹钟信号
kill(getpid(), SIGALRM);
printf("正常执行\n");
getchar();
}

信号屏蔽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
// 信号处理函数
void inthandler(int sig){
printf("产生一个信号\n");
}
int main(void){
// 定义一个信号动作结构体
struct sigaction act = {};
// 设置信号处理函数
act.sa_handler = inthandler;


// 设置信号标志
act.sa_flags = 0;
// 设置信号屏蔽集
sigemptyset(&act.sa_mask);
// 注册信号
sigaction(SIGINT,&act,0);
while (1){
printf("%d \n",getpid());
sleep(1);
}
return 0;
}

动态与静态

动态

配置文件tasks.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: gcc 生成活动文件",
"command": "/usr/bin/gcc",
"args": [
"-g",
"${file}",
"-ldl",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "调试器生成的任务。"
}
],
"version": "2.0.0"
}

简单示列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
///////////////////fun.h/////////////////
int fun(int a,int b);
///////////////////fun.c/////////////////
#include "fun.h"

int fun(int a,int b)
{
return a+b;
}
////////////////////main.c///////////////
include <stdio.h>
#include "fun.h"

int fun(int a,int b)
{
return a+b;
}

int main()
{
printf(" :%d \n",fun(10,30));
}


// 1.生成动态库
// 1.1 编译fun.c生成共享库
// gcc -fPIC -shared fun.c -o libfun.so

// 2. 生成可执行文件
// gcc main.c -L. -lfun -o main

// 3.1 需要将动态库加入到系统目录下
// cp libfun.so /usr/lib

// 3.2 加入到LD_LIBRARY_PATH
// export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)

示列2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <dlfcn.h>

// 链接时需要加入 libdl.so 库
// -ldl
int main()
{
void *handle;
handle = dlopen("./libfun.so",RTLD_LAZY);
if(handle == NULL)
{
printf("open error :%s\n",dlerror());
return 0;
}
// 解析符号
typedef int (*FUN)(int,int);
FUN pfun = dlsym(handle,"fun");
if(pfun == NULL)
{
printf("open error :%s\n",dlerror());
dlclose(handle);
return 0;
}
// 调用so中函数fun
printf(" %d \n",pfun(10,20));
dlclose(handle);
}

// gcc -g mian.c -ldl -o main

Linux Shell编程

Linux MakeFile

Linux ELF文件格式