文章目录
- 一、同步消息和异步消息传递的区别?
- (1)同步与异步消息的区别
- (2)同步和异步消息传递的优点和缺点
- 二、volatile变量
- (1)Linux C中多线程与volatile变量
- (2)linux C 中的volatile使用
- 三、mdadm工具--搭建软RAID
- (1)Raid含义
- (2)什么是RAID 0中的条纹?
- (3)要求
- (4)安装mdadm来管理RAID
- 第1步:更新系统并安装mdadm以管理RAID
- 第2步:验证附加的两个20GB驱动器
- 第3步:创建RAID的分区
- 第4步:创建RAID md设备
- 第5步:将RAID设备分配给文件系统
- 第6步:保存RAID配置
- 四、gettid和pthread_self区别
- 五、采用dlopen、dlsym、dlclose加载动态链接库
- 1.生产动态链接库
- 2.dlopen、dlsym函数介绍
- 六、sysconf函数
- 七、Linux中ifreq 结构体分析和使用 及其在项目中的简单应用
- 1.结构原型:
- 2.基本介绍
- 3.举例说明:
- 4.其它eg,参考:
- 参考
一、同步消息和异步消息传递的区别?
(1)同步与异步消息的区别
- 同步消息
同步消息传递涉及到等待服务器响应消息的客户端。消息可以双向地向两个方向流动。本质上,这意味着同步消息传递是双向通信。 即发送方向接收方发送消息,接收方接收此消息并回复发送方。发送者在收到接收者的回复之前不会发送另一条消息。
同步执行的特征为:在两个通信应用系统之间必须要进行同步, 两个系统必须都在正常运行, 并且会中断客户端的执行流, 转而执行调用。发送程序和接收程序都必须一直做好相互通信的准备。发送程序首先向接收程序发起一个请求(发送消息)。发送程序紧接着就会堵塞它自身的进程, 直到收到接收程序的响应。 发送程序在收到响应后会继续向下进行处理。 - 异步消息
异步消息传递涉及不等待来自服务器的消息的客户端。事件用于从服务器触发消息。因此,即使客户机被关闭,消息传递也将成功完成。异步消息传递意味着,它是单向通信的一种方式,而交流的流程是单向的。
当使用异步消息传送时, 调用者在发送消息以后可以不用等待响应, 可以接着处理其他任务。对于异步通信, 一个应用程序(请求者或发送者)将请求发送给另一个应用程序, 然后可以继续向下执行它自身的其他任务。发送程序无须等待接收程序的执行和返回结果, 而是可以继续处理其他请求。与同步方式不同, 异步方式中两个应用系统(发送程序和接收程序)无须同时都在运行, 也无须同时都在处理通信任务。
(2)同步和异步消息传递的优点和缺点
- 异步消息传递有一些关键优势。它们能够提供灵活性并提供更高的可用性——系统对信息采取行动的压力较小,或者以某种方式立即做出响应。另外,一个系统被关闭不会影响另一个系统。例如,电子邮件——你可以发送数千封电子邮件给你的朋友,而不需要她回复你。
- 异步的缺点是它们缺乏直接性。没有直接的相互作用。考虑一下与你的朋友在即时通讯或电话上聊天——除非你的朋友及时回复你,否则这不是聊天或谈话。
二、volatile变量
(1)Linux C中多线程与volatile变量
- volatile 修饰的变量表示改变量的值是易变的,编译器不对其进行优化,访问该变量的时候不会从寄存器读取, 而是直接从内存读取变量。
- 在多线程环境下,每个线程都有一个独立的寄存器,用于保存当前执行的指令。假设我们定义了一个全局变量,每个线程都会访问这个全局变量,这时候线程的寄存器可能会存储全量变量的当前值用于后续的访问。
- 当某个线程修改了全局变量的值时,系统会立即更新该线程寄存器中对应的值,其他线程并不知道这个全局变量已经修改,可能还是从寄存器中获取这个变量的值,这个时候就会存在不一致的情况。
- 针对多线程访问共享变量而且变量还会经常变化的情况,利用volatile类型修饰变量是一个很好的选择,如volatile int size = 10; 当多线程访问这个变量时,它会直接从size对应的地址访问,而不会从线程对应的寄存器访问,这样就不会出现前面说到的
- 同一变量的值在多个线程之间不一致的情况。
- eg如下:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
/* volatile变量控制线程的运行与结束 */
static volatile int do_run_thread = 1;
static pthread_t thread_tid;
static void *work_thread(void *arg)
{
while (do_run_thread) {
printf("thread is running...\n");
sleep(1);
}
printf("stop thread done!\n");
}
static void start_thread()
{
printf("start thread...\n");
pthread_create(&thread_tid, NULL, work_thread, NULL);
}
static void stop_thread()
{
printf("stop thread...\n");
do_run_thread = 0;
pthread_join(thread_tid, NULL); /* 等待线程结束 */
}
int main()
{
start_thread();
sleep(5);
stop_thread();
return 0;
}
(2)linux C 中的volatile使用
- 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
- 假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile:它可能被意想不到地改变。const:程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
三、mdadm工具–搭建软RAID
(1)Raid含义
- RAID是廉价磁盘的冗余阵列,用于在大规模环境中,需要的数据比正常使用被保护的高可用性和可靠性。 Raid只是一个池中的磁盘集合,成为一个逻辑卷并包含一个数组。 组合驱动程序创建一个数组或称为一组(组)。
- 可以创建RAID,如果有至少2个磁盘连接到RAID控制器,并且创建一个逻辑卷或更多的驱动器可以根据定义的RAID级别添加到阵列中。 软件Raid可以不使用物理硬件,这些被称为软件RAID。 软件Raid将被命名为Poor man raid。
- 在Linux中设置RAID0
使用RAID的主要概念是从单点故障保存数据,意味着如果我们使用单个磁盘存储数据,如果它失败,那么没有机会获得我们的数据,为了停止数据丢失我们需要一个容错方法。 所以,我们可以使用一些磁盘集合来形成RAID集。
其它:https://www.howtoing.com/understanding-raid-setup-in-linux/
(2)什么是RAID 0中的条纹?
Stripe通过划分内容在同一时间跨多个磁盘分割数据。 假设我们有两个磁盘,如果我们将内容保存到逻辑卷,它将通过划分内容保存在两个物理磁盘下。 为了获得更好的性能的RAID 0将被使用,但是,如果该驱动器的一个出现故障,我们不能得到的数据。 因此,使用RAID 0不是一个好的做法。唯一的解决方案是安装操作系统与RAID0应用逻辑卷,以保护您的重要文件。
RAID 0四个特点如下:
RAID 0具有高性能。
RAID 0中的容量损失为零。不会浪费空间。
零容错(如果任何一个磁盘出现故障,则无法恢复数据)。
写作和阅读将是极好的。
(3)要求
磁盘最小数目允许创建RAID 0,但你可以添加更多的磁盘,但顺序应该是两倍2,4,6,8,如果你有一个物理RAID卡有足够的端口,你可以添加更多磁盘。
这里我们不使用硬件RAID,此设置仅取决于软件RAID。 如果我们有一个物理硬件RAID卡,我们可以从它的效用UI访问它。 有些主板在默认情况下RAID功能在建,还有UI可以使用Ctrl + I键访问。
(4)安装mdadm来管理RAID
在这里这部分,我们将看看我们如何能够创建并使用名为SDB和SDC 2 20GB硬盘的Linux系统或服务器安装软件RAID0或分拆
- 我的服务器设置
Operating System : CentOS 6.5 Final
IP Address : 192.168.0.225
Two Disks : 20 GB each
第1步:更新系统并安装mdadm以管理RAID
1.前Linux上设置RAID0,让我们做一个系统更新,然后安装“mdadm的 '包。 mdadm是一个小程序,它将允许我们在Linux中配置和管理RAID设备。
# yum clean all && yum update
# yum install mdadm -y
安装mdadm工具
第2步:验证附加的两个20GB驱动器
2.在创建RAID 0,请务必确认检测到所连接的两个硬盘与否,使用下面的命令。
# ls -l /dev | grep sd
检查硬盘驱动器
- 3.一旦检测到新的硬盘驱动器,它的时间来检查连接的驱动器是否已经使用任何现有的RAID以下“的mdadm’命令的帮助。
# mdadm --examine /dev/sd[b-c]
检查RAID设备
从输出,我们就知道,没有RAID的已应用到这两个深发展和SDC驱动器。
第3步:创建RAID的分区
4.现在创建的SDB和SDC分区对于RAID,具有以下fdisk命令的帮助。 在这里,我将展示如何创建sdb的驱动器上的分区。
# fdisk /dev/sdb
按照以下说明创建分区。
按“N”来创建新的分区。
然后选择主分区“P”。
接下来选择分区号为1。
只需按两次回车键给出的默认值。
接着按“P”来打印定义的分区。
创建分区
按照以下关于在分区上创建Linux raid auto的说明。
按“L”,列出所有可用的类型。
键入“T”来选择分区。
选择“ 的fd”为Linux RAID自动,然后按Enter应用。
然后再次使用“P”打印什么,我们所做的更改。
使用“W”写的变化。
在Linux中创建RAID分区
注 :请按照相同的上述指示立即创建SDC驱动器上的分区。
5.创建分区后,验证这两个驱动程序都支持RAID使用以下命令正确定义
# mdadm --examine /dev/sd[b-c]
# mdadm --examine /dev/sd[b-c]1
验证RAID分区
第4步:创建RAID md设备
6.现在创建MD设备(即/ dev / md0的 ),使用下面的命令适用的RAID级别。
# mdadm -C /dev/md0 -l raid0 -n 2 /dev/sd[b-c]1
# mdadm --create /dev/md0 --level=stripe --raid-devices=2 /dev/sd[b-c]1
-C -创建
-l -水平
-n -没有RAID-设备
7.一旦MD设备已经建立,现在验证RAID级别 , 设备和阵列的使用状态,具有如下面的一系列命令的帮助。
# cat /proc/mdstat
验证RAID级别
# mdadm -E /dev/sd[b-c]1
验证RAID设备
# mdadm --detail /dev/md0
验证RAID阵列
第5步:将RAID设备分配给文件系统
8.创建一个RAID设备/ dev / md0的一个EXT4文件系统,并在/ dev / RAID0安装它。
# mkfs.ext4 /dev/md0
创建ext4文件系统
9.一旦EXT4文件系统已经为RAID设备创建的,现在创建一个挂载点目录(即到/ mnt / RAID0),并在其挂载设备/ dev / md0的 。
# mkdir /mnt/raid0
# mount /dev/md0 /mnt/raid0/
10.接下来,验证设备/ dev / md0的是/ mnt / RAID0目录下使用df命令安装。
# df -h
11.接下来,挂载点的/ mnt / RAID0下创建一个名为“howtoing.txt’文件,添加一些内容到创建的文件,并查看文件和目录的内容。
# touch /mnt/raid0/howtoing.txt
# echo "Hi everyone how you doing ?" > /mnt/raid0/howtoing.txt
# cat /mnt/raid0/howtoing.txt
# ls -l /mnt/raid0/
验证安装设备
12.一旦你验证挂载点,它的时间来创建在/ etc / fstab文件中的fstab条目。
# vim /etc/fstab
按照描述添加以下条目。 可能根据您使用的安装位置和文件系统而有所不同。
/dev/md0 /mnt/raid0 ext4 defaults 0 0
将设备添加到Fstab
13.运行安装’-a’来检查,如果在fstab条目的任何错误。
# mount -av
检查Fstab中的错误
第6步:保存RAID配置
14.最后,突袭配置保存到文件中的一个,以保持配置以供将来使用。 同样,我们使用’mdadm的“与”-s“(扫描)和”-v“(详细)选项命令,如图所示。
# mdadm -E -s -v >> /etc/mdadm.conf
# mdadm --detail --scan --verbose >> /etc/mdadm.conf
# cat /etc/mdadm.conf
保存RAID配置
就是这样,我们在这里看到,如何使用两个硬盘配置raid级别的RAID0条带化。 在接下来的文章中,我们将看到如何建立RAID5。
四、gettid和pthread_self区别
- gettid 获取的是内核中真实线程ID, 对于多线程进程来说,每个tid实际是不一样的。
- 而pthread_self获取的是相对于进程的线程控制块的首地址, 只是用来描述统一进程中的不同线程。pthread_self 即是获取线程控制块tcb首地址,相对于进程数据的段的偏移, 注:pthread_create也是返回该值。
- gettid 获取的是内核中线程ID,而pthread_self 是posix描述的线程ID。
- 对于单线程的进程,内核中tid==pid,对于多线程进程,他们有相同的pid,不同的tid。tid用于描述内核真实的pid和tid信息。
- pthread_self返回的是posix定义的线程ID,man手册明确说明了和内核线程tid不同。它只是用来区分某个进程中不同的线程,当一个线程退出后,新创建的线程可以复用原来的id。
- 为什么需要两个ID描述线程?通过执行如下代码, 我们也能发现他们的区别:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
//#include <sys/syscall.h>
#define __NR_gettid 186
void *f()
{
int status;
printf("begin: pid: %d, tid:%ld, self: %ld\n", getpid(), (long int)syscall(__NR_gettid), pthread_self());
int ret = fork();
if(ret == 0){
printf("[child] pid: %d, tid:%ld, self: %ld\n", getpid(), (long int)syscall(__NR_gettid), pthread_self());
}else if(ret > 0){
printf("[parent] pid: %d, tid:%ld, self: %ld\n", getpid(), (long int)syscall(__NR_gettid), pthread_self());
waitpid(-1, &status, 0);
}
}
int main()
{
int i = 0;
pthread_t pth[1];
while(i++<1){
pthread_create(&pth[i], NULL, f, NULL);
sleep(1);
}
pause();
}
描述线程的id,为什么需要两个不同的ID呢?
这是因为线程库实际上由两部分组成:内核的线程支持+用户态的库支持(glibc),Linux在早期内核不支持线程的时候,glibc就在库中(用户态)以线程(就是用户态线程)的方式支持多线程了,POSIX thread只要求了用户编程的调用接口,对内核接口没有要求。
linux上的线程实现就是在内核支持的基础上以POSIX thread的方式对外封装了接口,所以才会有两个ID的问题。
例子中,在线程中调用fork,只会将当前活动线程设置为活动(其他线程终止),且进程使用的都是虚拟地址,所以产生的pthread_self() 是相同的。
上述不匹配,对程序的实际运行,并没有影响,因为他们的tid是不同的。
五、采用dlopen、dlsym、dlclose加载动态链接库
1.生产动态链接库
编译参数 gcc -fPIC -shared
例如将如下程序编译为动态链接库libcaculate.so,程序如下:
int add(int a,int b)
{
return (a + b);
}
int sub(int a, int b)
{
return (a - b);
}
int mul(int a, int b)
{
return (a * b);
}
int div(int a, int b)
{
return (a / b);
}
复制代码
编译如下: gcc -fPIC -shared caculate.c -o libcaculate.so
2.dlopen、dlsym函数介绍
在linux上man dlopen可以看到使用说明,函数声明如下:
#include <dlfcn.h>
void *dlopen(const char *filename, int flag);
char *dlerror(void);
void *dlsym(void *handle, const char *symbol);
int dlclose(void *handle);
dlopen以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程,dlerror返回出现的错误,dlsym通过句柄和连接符名称获取函数名或者变量名,dlclose来卸载打开的库。 dlopen打开模式如下:
RTLD_LAZY 暂缓决定,等有需要时再解出符号
RTLD_NOW 立即决定,返回前解除所有未决定的符号。
采用上面生成的libcaculate.so,写个测试程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
//动态链接库路径
#define LIB_CACULATE_PATH "./libcaculate.so"
//函数指针
typedef int (*CAC_FUNC)(int, int);
int main()
{
void *handle;
char *error;
CAC_FUNC cac_func = NULL;
//打开动态链接库
handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
//清除之前存在的错误
dlerror();
//获取一个函数
*(void **) (&cac_func) = dlsym(handle, "add");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf("add: %d\n", (*cac_func)(2,7));
cac_func = (CAC_FUNC)dlsym(handle, "sub");
printf("sub: %d\n", cac_func(9,2));
cac_func = (CAC_FUNC)dlsym(handle, "mul");
printf("mul: %d\n", cac_func(3,2));
cac_func = (CAC_FUNC)dlsym(handle, "div");
printf("div: %d\n", cac_func(8,2));
//关闭动态链接库
dlclose(handle);
exit(EXIT_SUCCESS);
}
编译选项如下:gcc -rdynamic -o main main.c -ldl
测试结果如下所示:
注意:
void *dlsym(void *handle, const char *symbol);
返回值为void*
(void **)&(cac_func)是将函数指针的地址强制转换void**类型
然后使用*取值,获取dlsym的返回值
实际这个地方没有必要这样,函数指针本来就是地址,可以直接用
cac_func = dlsym(handle, "add");
六、sysconf函数
在看开源代码的时候,尤其是获取cpu核数的时候,发现了一个很好用的一个函数
#include <unistd.h>
long sysconf(int name);
通过名字可以猜到,该函数是获取一些系统的参数。
使用下面的一个实例,看一下我的电脑的一些配置信息
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("Size of a page in bytes:%ld\n",sysconf(_SC_PAGESIZE));
printf("Max length of a hostname:%ld\n",sysconf(_SC_HOST_NAME_MAX));
printf(" The maximum number of files that a process can have open at any time.:%ld\n",sysconf(_SC_OPEN_MAX));
printf(" The number of clock ticks per second.:%ld\n",sysconf(_SC_CLK_TCK));
printf("The number of processors currently online .:%ld\n",sysconf(_SC_NPROCESSORS_ONLN));
printf("The number of processors configured..:%ld\n",sysconf(_SC_NPROCESSORS_CONF));
return 0;
}
输出信息:
Size of a page in bytes:4096
Max length of a hostname:64
The maximum number of files that a process can have open at any time.:1024
The number of clock ticks per second.:100
The number of processors currently online .:1
The number of processors configured..:1
七、Linux中ifreq 结构体分析和使用 及其在项目中的简单应用
1.结构原型:
/*
* Interface request structure used for socket
* ioctl's. All interface ioctl's must have parameter
* definitions which begin with ifr_name. The
* remainder may be interface specific.
*/
struct ifreq
{
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
} ifr_ifrn;
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
void __user * ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
#define ifr_name ifr_ifrn.ifrn_name /* interface name */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
#define ifr_addr ifr_ifru.ifru_addr /* address */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
#define ifr_flags ifr_ifru.ifru_flags /* flags */
#define ifr_metric ifr_ifru.ifru_ivalue /* metric */
#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
#define ifr_map ifr_ifru.ifru_map /* device map */
#define ifr_slave ifr_ifru.ifru_slave /* slave device */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */
#define ifr_newname ifr_ifru.ifru_newname /* New name */
#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/
2.基本介绍
ifreq结构定义在/usr/include/net/if.h,用来配置ip地址,激活接口,配置MTU等接口信息的。其中包含了一个接口的名字和具体内容——(是个共用体,有可能是IP地址,广播地址,子网掩码,MAC号,MTU或其他内容)。ifreq包含在ifconf结构中。而ifconf结构通常是用来保存所有接口的信息的。
3.举例说明:
在Linux系统中,ifconfig命令是通过ioctl接口与内核通信,
例如,当系统管理员输入如下命令来改变接口eth0的MTU大小:
ifconfig eth0 mtu 1250
ifconfig命令首先打开一个socket,然后通过系统管理员输入的参数初始化一个数据结构,并通过ioctl调用将数据传送到内核。SIOCSIFMTU是命令标识符。
struct ifreq data;
fd = socket(PF_INET, SOCK_DGRAM, 0);
< ... initialize "data" ...>
err = ioctl(fd, SIOCSIFMTU, &data);
4.其它eg,参考:
参考
https://www.jb51.net/article/117873html https://www.jianshu.com/p/86ed6a7cf34b
https://www.howtoing.com/create-raid0-in-linux/