Linux链表list_head/hlist_head/hlist_nulls_head的并发性

linux内核hlist分析 在Linux内核中,hlist(哈希链表)使用非常广泛。本文将对其数据结构和核心函数进行分析。 和hlist相关的数据结构有两个(1)hlist_head (2)hlist_node struct hlist_head { struct hlist_n 阅读详情

我们被灌输过各种高效复杂的数据结构,比如rb tree,skip list等等,但现实中,我们经常用各种List管理我们的数据,因为它的操作非常简单。

  • 如果数据量小或者不太在乎时间,选择list_head。
  • 如果数据量大且对查找性能要求很高,读多写少的情况下,选择hlist。
  • 如果数据量大且对查找性能要求很高,写操作相对多的情况下,选择hlist_nulls。

在涉及并发操作的环境中,对list的add,delete肯定需要某种并发保护,因为:

  • 无论是add还是delete操作本身均不是原子操作。

但在具体的使用中,很多人搞不清该使用哪种类型的锁,于是就出现了以下的范式:

  • 懒省事者,无论是add,delete还是list_for_each,均用一把spinlock搞定。
  • 想优化性能却不想做太多的,直接把spinlock换成rwlock,一切看起来OK。
  • 如果写操作非常多且不能忍受性能损失,rculock搞定,同时保留add,delete的spinlock。

但是事情真的有这么复杂吗?我看未必。

事实上, 只要我们自己控制好写并发,读写是可以无锁并行的,只要保证我们的读操作中遍历链表的顺序是从前到后即可。

我们看看这是为什么,先看add list:

å¨è¿éæå¥å¾çæè¿°

我们看到,之所以add操作对遍历不影响,是因为:

  • 第一,遍历是始终单向向前的。
  • 第二,先将新节点的next接入链表,使从new节点可继续遍历。
  • 第三,再将链表节点的next指向新节点,使new节点可被遍历到。

同时,我们知道在x86_64体系,对齐的指针操作是原子操作,所以,上面第三步就是原子操作,这保证了链表add操作和遍历操作是无冲突的:

  1. 遍历进程要么到达new。
  2. 遍历进程要么越过到达N2。
  3. 不会存在除1和2之外的其它情况。

然而,如何保证上图中的顺序不会被编译器或者CPU给reorder呢?这很容易保证:

  • 增加一个屏障即可!

长话短说,几乎 可以认为,x86_64体系,只有write-read序列存在CPU乱序的情况,所以乱序的情况并不会发生在链表操作中, “几乎” 以外的情况,参见:
https://blog.youkuaiyun.com/dog250/article/details/103911008

在Linux内核的链表操作接口中,上述的顺序以及原子保证是通过 _rcu 后缀来修饰的接口保证的,比如:

  • list_add_rcu/list_del_rcu
  • hlist_add_after_rcu/hlist_del_rcu
  • hlist_nulls_add_head_rcu/hlist_nulls_del_rcu

以上的这些接口可以同时和同版本的for_each遍历同时进行而不会出现并发问题!也就是说,在保证不会同时add和del的前提下,可以实现无锁遍历。

有了上面的理解,我们再看看del操作的流程:

å¨è¿éæå¥å¾çæè¿°

和add操作一样,这也是个实实在在的RCU操作,其核心在于 最后更新 !

关于rcu,我们必须清楚一件事,那就是遍历操作本身要通过rcu readlock保护,保证delete的节点在rcu readlock释放后再回收。

最后,我们看一个关于hlist_nulls的遍历算法和其Insert/add,Remove/delete算法,来自内核文档Documents/RCU/rculist_nulls.txt:

Lookup算法:
 

1) Lookup algo
--------------
rcu_read_lock()
begin:
obj = lockless_lookup(key);
if (obj) {
  if (!try_get_ref(obj)) // might fail for free objects
    goto begin;
  /*
   * Because a writer could delete object, and a writer could
   * reuse these object before the RCU grace period, we
   * must check key after getting the reference on object
   */
  if (obj->key != key) { // not the object we expected
     put_ref(obj);
     goto begin;
   }
}
rcu_read_unlock();

Insert/add算法:

2) Insert algo :
----------------
/*
 * Please note that new inserts are done at the head of list,
 * not in the middle or end.
 */
obj = kmem_cache_alloc(...);
lock_chain(); // typically a spin_lock()
obj->key = key;
/*
 * we need to make sure obj->key is updated before obj->next
 * or obj->refcnt
 */
smp_wmb();
atomic_set(&obj->refcnt, 1);
hlist_add_head_rcu(&obj->obj_node, list);
unlock_chain(); // typically a spin_unlock()


Remove/delete算法:
 

3) Remove algo
--------------

if (put_last_reference_on(obj) {
   lock_chain(); // typically a spin_lock()
   hlist_del_init_rcu(&obj->obj_node);
   unlock_chain(); // typically a spin_unlock()
   kmem_cache_free(cachep, obj);
}


在Linux内核的socket系统中,我们可以看到上述算法的具体实现,这是一个标准的算法,但是在特殊的环境下真的有必要这么复杂吗?lock_chain真的有必要吗?

以socket系统为例,我们知道,socket是可以在内核中的软中断上下文被创建和销毁的,这里就会存在并发问题,因此Insert/Insert,Remove/Remove,Insert/Remove之间就会存在并发,所以必须lock_chain。

但是如果我们保证节点的Insert和Remove均是在进程上下文的受控环境中进行的,比方说配置下发,那么我们只需要控制下发过程本身的并发即可,我们只要保证不会同时有Insert/Insert,Remove/Remove,Insert/Remove之间的并发即可,而这可以通过mutex来实现:

int write_config(...)
{
    mutext_lock(&global_mutex);
    insert_or_remove_some_nodes(...);
    mutex_unlock(&global_mutex);
}


这样我们就可以省掉更多的spin开销,而我们知道mutex是可以re-schedule的,这便节省了CPU资源。

你也不要怼我说re-schedule切换进程也是有开销的,这个开销可能比spinlock的开销更大,我承认这个点,但是我并非站在这个谁的开销大的立场上说这件事的,我的立场是:

  • 受控的环境比争抢的环境更好,我一向崇尚仲裁。

至于怎么定义 “好” ,这便是形而上学的范畴了。

举个例子,Linux内核的路由rule就是这么实现的。

Linux协议栈-netfilter(2)-conntrack 连接跟踪(conntrack)用来跟踪和记录一个连接的状态,它为经过协议栈的数据包记录状态,这为防火墙检测连接状态提供了参考,同时在数据包需要做NAT时也为转换工作提供便利。本文基于Linux内核2.6.31实现的conntrack进行源码分析。1. conntrack模块初始化1.1 conntrack模块入口conntrack模块的初始化主要就是为必要的全局数据结构进行初始化,代码流程如下:上... 阅读详情

相关推荐

windows11 远程桌面连接 通过代理连接RDP桌面报错 错误代码0x904 拓展错误代码0x7

打开windows Defender防火墙设置--允许应用通过防火墙更改设置--允许其他应用浏览--进入C:\WINDOWS\system32目录选择mstsc.exe进入C:\Windows\SysWOW64目录h'qmstsc.exe添加后列表里多了一个远程桌面连接,还有一个Remote Desktop Connection,配置网络权限后保存连接成功。

weixin_43978891的博客 1万+

Kernel base Series(3)-----hlist_nulls_head

分类: 原文地址:Kernel base Series(3)-----hlist_nulls_head 作者:mtloveft 声明:本文为原创 #####请转贴时保留以下内容###### 作者:GTT 本文档归属http://oldtown.cublog.cn/.转载请注明出处! 请提出宝贵意见Mail:mtloveft@hotmail.com Linux

wolongzhumeng的专栏 1192

从波形到代码:手把手调试GaN图腾柱PFC的过零点切换与软启动策略

本文详细解析了GaN图腾柱无桥Boost PFC电路在过零点切换与软启动策略中的调试方法。通过波形分析、代码实现和寄存器配置,提供了解决误开通、震荡波形等问题的实用方案,帮助工程师优化电源设计效率与可靠性。

weixin_30831529的博客 424

哈希链表及其变种

前言 先来直观的比较下普通链表和哈希链表: 普通链表 普通链表的表头和节点相同 struct list_head { struct list_head *next, *prev; }; 哈希链表 哈希链表头 struct hlist_head { struct hlist_node *first; }; 哈...

weixin_30824277的博客 462

比较器和运算放大器-2

今天继续了解输入阻抗问题,观察下图:首先考虑在我们没有接运放时,A点输出电压为6V,当接入如图所示的阻抗时电压还是6V吗?共模电阻和红框内对地电阻并联,阻值变小,相当于5k,通过计算可以求得A点电压变为4V,这样A的值发生了改变,试想一下这样的信号接入到运放的两端是不是相当于比较的两端信号发生了改变,那结果一定会发生偏离。信号源一般内阻是不确定的,一般也不是很小,如果不想改变输入阻抗,那唯一的办法就是改变运放的内阻了,比如内阻改为100K,1M等,越大越好,是不是A点电压就会无限接近6V。

weixin_42855218的博客 1311

C51:PWM+舵机角度控制

PWM,英文名Pulse Width Modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码,也就是说通过调节占空比的变化来调节信号、能量等的变化,占空比就是指在一个周期内,信号处于高电平的时间占据整个信号周期的百分比,例如方波的占空比就是50%.脉冲宽度调制通过占空比编码模拟信号一个周期内,高电平占据时长的百分比如下图所示,最便宜的舵机sg90,常用三根或者四根接线,黄色为PWM信号控制。

qq_73379310的博客 3388

Linux 连线跟踪流程整理(linux-2.6.31)

author: jonathan 本文档的CopyRight归jonathan所有,可自由转载,转载时请保持文档的完整性。 /*----------------------------------------------------------------------------------------------------------------------------*/ Linux

h_cszc的专栏 1269

uniapp开发小程序保存图片权限获取失败一直走fail?本地能保存,线上版就不行! 原来是这个问题!

通过这次经历,我发现线上版小程序和开发版、体验版的权限控制差异较大。如果你也遇到类似的问题,首先要检查是否已勾选“采集用户隐私”,并确保在提交审核时配置了隐私条款。这个设置非常关键,避免像我一样浪费了不少时间和版本。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1077

linux 其他版本RCU

linux 其他版本RCU

一叶知秋的博客 342

Linux系统安装部署Redis完整教程(图文详解)

最近需要在Linux服务器安装部署Redis,参考了网上很多的文章,比较零碎,所以我自己写了一篇完整的Redis安装部署文档,在部署Redis之前重装了我的华为服务器,就是为了给大家一步步阐述我的操作过程,配合图文进行讲解更加通俗易懂!

黄团团的个人博客 6692

ARFoundation系列讲解 - 手部跟踪编程

通过使用ARFoundation的手部跟踪子系统,我们可以轻松地在AR应用程序中实现与用户手部的交互。通过设置AR环境、创建场景和相机、添加手部跟踪功能以及编写相应的脚本,我们可以开始在AR场景中跟踪和操作用户的手部。在Unity中创建一个新场景,并将AR Session组件添加到场景中的一个空对象上。手部跟踪的具体实现可能因AR设备和平台的不同而有所差异,你可能需要根据你的使用情况进行一些适配和调整。当你将手部放入AR场景中时,手部跟踪系统将触发相应的事件,并调用脚本中的回调函数。

2301_79325339的博客 355

Linux 链表 list_head

list_head

weixin_48006170的博客 1365

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

LIST_HEAD_INIT分析

#define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name)    用LIST_HEAD(name)声明一个名为name的链表头,它的next、prev指针都初始化为指向自己,这样,实际上就建立了一个空链表,以上

wrqisql的专栏 4276

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

linux内核链表list_head的使用技巧和原理

这里的链表节点只含有链表指针(next和prev),没有链表的数据

Maisu的博客 857

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Linux 内核学习之list_head

1.

weixin_43799986的博客 2160

Linux内核学习之list链表

Linux内核中的链表定义为:struct list_head 初始化链表 单纯定义并初始化链表的两种方式: 方式一: LIST_HEAD(mylist); 方式二: struct list_head mylist = LIST_HEAD_INIT(mylist); 当链表嵌入到结构体中时,需要用另外的方式进行初始化 比如,定义如下结构体: typedef struct...

houyongqian88的专栏 397

详解linux内核链表list_head

Linux内核中的链表通常都组织成双向循环链表,不同于一般意义上的链表,这里的链表节点只含有链表指针(next和prev),没有链表的数据。Linux内核中使用的链表源代码位于` include/linux/list.h`中,下面详细叙述。

Leon的博客 1323

Linux内核中经典链表 list_head 常见使用方法解析

做内核驱动开发经常会使用linux内核最经典的双向链表 list_head, 以及它的拓展接口(或者宏定义): list_add , list_add_tail, list_del , list_entry ,list_for_each , list_for_each_entry … 每次看到这些接口,感觉都很像,今天专门研究了一下内核,对它们做一些总结,希望为后续开发提供方便。 首先找到list...

wanggong_1991的博客 803

Linux旧版本内核nf_conntrck BUG一则

写这个已经没有实际意义了,毕竟本文涉及的是一个Linux 3.10老旧版本内核的一个nf_conntrack的BUG,之所以还是写下来是因为它既有情怀,又有意思。 BUG的描述非常简单,就是在____nf_conntrack_find函数里面访问NULL指针系统崩溃了。 我的任务找出它。 这次不同于手艺人的做法,而是采用经理的作风来排查故障。所以,这次不用crash工具,不用stap,不用trac...

TCP/IP 5934

C语言:Linux内核中经典双向链表 list_head 常见使用方法解析

做内核驱动开发经常会使用linux内核最经典的双向链表list_head, 以及它的拓展接口(或者宏定义): list_add , list_add_tail, list_del , list_entry ,list_for_each , list_for_each_entry ......每次看到这些接口,感觉都很像,今天专门研究了一下内核,对它们做一些总结,希望为后续开发提供方便。首先找到list_head 结构体定义,kernel/inclue/linux/types.h 如下:struct。

weixin_44498318的博客 1576

Linux 内核 hlist 详解

在Linux内核中,hlist(哈希链表)使用非常广泛。本文将对其数据结构和核心函数进行分析。 和hlist相关的数据结构有两个:hlist_head 和 hlist_node //hash桶的头结点 struct hlist_head { struct hlist_node *first;//指向每一个hash桶的第一个结点的指针 }; //hash桶的普通结点 struct hl

hshopeful 3万+

Linux协议栈优化之Netfilter分类conntrack

首先,如果你不同意我以下的观点,本文就不必看了:如今内存不值钱,空间换时间很划算,要知道,一万年前的1秒和一万年后1秒是一样的,你要是觉得人们做的事情可能不同,但是请记住,永远都会发生“安迪给你的被比尔拿走”之类的事情,对于你而言,什么都没有改变!       虽然空间可以被拓展,但是要讲技巧。以下是一张截图,测试的是http服务器的常规性能,我依然使用相对值比较,因为我的电脑压不出真实性能,另外

TCP/IP 1万+

[网络子系统] linux-2.6.35.6 nf_conntrack

原帖地址:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=4082396&fromuid=29353251 最近在整理以前的笔记,在Godbach的鼓励下,准备写一个有关nf_conntrack的总结。与之前写的iptables算是姐妹篇,因为iptables和nf_conntrack应该算是netfilter中比较重要的两个模

Denallo的专栏 2920

struct list_head/LIST_HEAD以及LIST_HEAD_INIT/INIT_LIST_HEAD

仅仅为struct list_head相关的两个宏: #define LIST_HEAD_INIT(name) { &amp;(name), &amp;(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) static inline void INIT_LIST_HEAD...

abcdefg的专栏 2030
上一篇: 理解内存屏障
下一篇: (九)洞悉linux下的Netfilter&iptables:网络地址转换原理之DNAT
废言Pro
博客等级 码龄16年 1093粉丝 142原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值