蛙蛙推荐:C语言入门之二&网赌平台哪个信誉好mdash;—编写第一个有意义的小程序

简介

  上次配置好了Linux+vim+gcc以及写了一个HelloWorld级别的示例程序,这次写一个稍微有意义的程序,在写这个小程序的过程中,我们快速的对C语言有一个大致的了解,SICP里指出,要学一门语言,要注意3个方面,一是这个语言提供了哪些Primitive,如数据类型,表达式,语句;二是提供了哪些组合规则,三是提供了哪些抽象机制,我们学C的时候也有意识的留意一下。

内核数据结构贯穿于整个内核代码中,这里介绍4个基本的内核数据结构。

需求分析

  同事们中午一般都一起出去吃午饭,AA制,但每次吃饭都现场算钱的话,比较麻烦,不如一人付一次,轮换着付钱,最终付的钱还是均匀的。但有的时候今天吃的多,明天吃的少,而且有的人今天来了,明天没来,所以要有个记账的软件,要记录下哪天都有谁去吃饭了,花了多少钱,打了多少折扣,当天是谁付的款,然后程序能自动算出来,谁付款付的多,谁付款付的少,付款付的最少的今天就主动付款。(大家可以了解下www.5dfantuan.com)

  我定义了一个文件格式,每个字段用”|”分隔,从左到右每列一次是吃饭日期,总消费金额,折扣,吃饭的人,付款人和付款金额。其中吃饭的人用逗号分隔,付款记录也用逗号分隔,每个付款记录用冒号分隔开付款人和付款金额。

2010-9-10|83|0.8|a,b,c,d|a:100,b:100
2010-9-11|102|0.8|a,b,c,d,e|b:100,c:50

 

比如以上的输入文件input.txt,9月10日花了83块钱,打了0.8折是66.4元,有4个人吃饭,分别是a,b,c,d,人均消费是66.4/4=16.6元,当天a和b各充了100元,那么今天a和b的余额就是100-16.6=83.4元,而c和d没付钱,余额就是-16.6元,下次就应该让他俩出钱。

利用这4个基本的数据结构,可以在编写内核代码时节约大量时间。

数据结构定义

  我们先进行数据结构的定义,在C里定义数据一般用struct来定义,c的struct不能定义函数(能定义函数指针),只能定义数据成员,而且不是原生支持的数据类型,使用类型的时候要加struct前缀。

  我们定义两个常量,MAX_RECORD_COUNT定义input.txt里最大的记录数(一行一个记录),因为C里要自己管理内存,分配数据等要考虑个最大值,不像c#里有ArrayList这样自动扩大的类,所以我们声明列表类型的数据一般用数组,数组要给定一个最大长度。MAX_ARRAY_COUNT,这个定义普通字符串的最大长度,如输入文件里各个字段的长度都不能超过这个长度。

网赌平台哪个信誉好 1网赌平台哪个信誉好 2data_structure.h

#define MAX_RECORD_COUNT 10
#define MAX_ARRAY_COUNT 15

struct people
{
   char name[MAX_ARRAY_COUNT];
};
struct pay_record
{
   struct people person;
   double amount;
};
struct account_record
{
   char date[MAX_ARRAY_COUNT];
   double discount;
   struct people person[MAX_ARRAY_COUNT];
   int people_count;
   struct pay_record payrecord[MAX_ARRAY_COUNT];
   int pay_record_count;
   int total_consumption;
};
struct account_record_list
{
   struct account_record records[MAX_RECORD_COUNT];
   int count;
};
struct person_consumption
{
   char name[MAX_ARRAY_COUNT];
   double consumption;
};
struct person_consumption_list
{
   struct person_consumption persons[MAX_ARRAY_COUNT];
   int count;

};

  如上,我们用struct
account_record来表示一天的记账记录,account_record_list表示多条这样的记录,我们的命名规则就是表示多条数据类的结构后缀名加_list,并有一个count的成员表示有效数据的长度。struct
account_record里各个成员分别对应输入文件里的各个字段,比如struct
people其实就是一个长度为15的字符数组,person_consumption表示每个人的余额。这里尽量不用typeof是因为那样有些乱。

  在这里我们用到了各种数据类型的定义,如单个值int,double,一维数组,结构定义等。

主要内容:

接口设计

  定义好了数据,就该定义操作这些数据的函数了,我们先从上层来分析都需要哪些模块,模块之间的依赖关系,以及模块里有哪些操作。首先因为我们定义了一个输入文件,就应该有一个模块来读取这个文件,并构建成内存里的消费记录,付款记录等对象,该模块就叫readinput吧。另外内存里有了消费记录,付款记录这些对象,就需要处理它们,计算出每个人的余额,某天的人均消费等,我们把这个模块叫record_handler,最后我们要有个主模块调用这两个模块,组合成最终的业务逻辑,并显示给用户,这个模块就叫main吧。

readinput.h

struct account_record_list read_input();

  该模块对外只提供一个方法read_input,返回一个消费记录列表类,其内部实现的私有函数不需要写在头文件里,因为没人用它,这也算起到了封装的作用,因为具体该函数的实现类是readinput.c,该文件最终会编译成一个.o文件,别人要想用该模块的功能的话,只要有readinput.o和readinput.h就行了,一般会把.o放到lib目录下,.h放到include目录下。

网赌平台哪个信誉好 3网赌平台哪个信誉好 4record_handler.h

void edit_person_consumption(struct person_consumption_list *list,
        const char *name,double money);
void print_person_consumption_list(const struct person_consumption_list list);
double calc_avg_consumption(double total, int person_count, double discount);

  该模块定义了对消费记录的处理,edit_person_consumption用来修改消费记录,比如某人吃饭消费了多少钱,某人付了多少钱,都调用它来计算出各个人的余额。print_person_consumption_list用来打印出每个人的余额,谁是正的余额,谁是负的余额,calc_avg_consumption用来根据总金额,折扣数和吃饭的人数计算出人均消费数。

  我们在设计模块时要尽量让模块的职责清晰,做到高内聚,尽量少的使用别的模块的功能,并尽量让很多的模块使用自己,还要考虑清楚模块之间的调用关系。

  Main模块不需要.h头,它是一个驱动模块,用来调用其它两个模块,完成整体的功能,不对外提供接口,但要实现一个main的入口函数。

  • 链表
  • 队列
  • 映射
  • 红黑树

主函数的实现

  每个可执行程序都要有一个main的方法,我们在main模块里定义,在使用前,先要用include来声明你都依赖哪些模块,只需要包含该模块的头文件就可以,尖括号括的是系统的头文件,会在/usr/include/下查找,引号括住的是自己的头文件,会在当前目录下查找。

 

网赌平台哪个信誉好 5网赌平台哪个信誉好 6代码

#include <stdio.h>
#include "data_structure.h"
#include "readinput.h"
#include "record_handler.h"

void print_account_record_list(const struct account_record_list list);
struct person_consumption_list handler_account_record_list(
        const struct account_record_list list);
int main()
{
    struct account_record_list list ;
    list = read_input();
    print_account_record_list(list);
    struct person_consumption_list consumption_list =
        handler_account_record_list(list);
    print_person_consumption_list(consumption_list);
    return 0;

}

  接下来我们声明两个main函数要用到的两个私有函数,因为c里要使用函数要先声明,否则你就只能用你这个函数上面定义的函数,我们在这里先声明两个私有函数的原型,print_account_record_list来打印出每条消费记录的细节,handler_account_record_list用来处理整个记录列表。在这里看到list参数有个const的修饰,该关键字可以保证调用的函数不会修改你的传入的变量,因为这两个方法一个用来打印,一个用来当作输入源计算一些值,从语义上来说就不应该会去修改该参数,所以我们加了const。c里使用并深入理解const关键字是老鸟和新手的一个标志,大家可以查查相关资料。

  main主函数一般都返回int,其中函数定义里可以省略掉int,默认就是int,里面的逻辑也很简单,读取消费记录,打印消费记录,处理消费记录得到每个人的余额状况,打印每个人的余额状况,逻辑非常清晰,下面就是每个子函数的具体实现了。

  下面这个私有函数用来处理消费记录,遍历每天的消费和充值记录,并修改每人的余额记录,逻辑也很清晰,很好的调用了record_handler模块提供的功能,使该函数的简单明了,职责明确。

网赌平台哪个信誉好 7网赌平台哪个信誉好 8代码

 struct person_consumption_list handler_account_record_list(
         const struct account_record_list list)
 {
     struct person_consumption_list consumption_list;
     consumption_list.count = 0;
     int i = 0, j = 0;
     for(i = 0; i < list.count; i++)
     {
         struct account_record record = list.records[i];
         double average_consumption =
             calc_avg_consumption(
                     record.total_consumption,
                     record.people_count,
                     record.discount);
         for(j = 0; j < record.people_count; j++)
         {
             edit_person_consumption(&consumption_list,
                     record.person[j].name,
                     -average_consumption);
             int k =0;
         }
         for(j = 0; j< record.pay_record_count; j++)
         {
             edit_person_consumption(&consumption_list,
                     record.payrecord[j].person.name,
                     record.payrecord[j].amount);
         }
     }
     return consumption_list;
 }

 

读取记账文件

  我们会用到IO,字符串以及一些字符串和数值转换的函数,所以先包含这些头文件。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "data_structure.h"

  C的编译器比较傻,有的时候你不包含头文件也能编译,但运行时会给个错误记录,比如atof是在stdlib.h里定义的,你不包含它也能编译,但你printf(“%f”,atof(“0.8”));它会给你显示0.0,你包含了就没事了,这个太无语了,在c#里你不引用dll就使用人家的方法,编译肯定出错,在C里却什么事都可能发生,所以最好把自己以前学的编程知识先扔到一边,当个编程初学者来学习C,感觉c比javascript还诡异。

  struct account_record_list
read_input()是一个比较大的函数,我们分开来看,先看变量定义部分,在C的函数里,变量定义要放在最前面,我们这里定义了fp一个文件类型指针,其中文件操作用c的标准库函数fopen,fclose操作,大家看下c手册就知道用法,这里是用只读方式打开,如果不存在则抛错。

网赌平台哪个信誉好 9网赌平台哪个信誉好 10代码

FILE *fp;
if((fp=fopen("input.txt","rt")) == NULL)
{
    printf("cannot open input.txt");
    getchar();
    exit(1);
}

int i = 0;
enum read_state {
    state_default,
    state_date,
    state_consumption,
    state_discount,
    state_person,
    state_payrecord
} state;
state = state_date;
struct account_record_list result;
result.count = 0;
struct account_record *p_record = result.records;
char temp_buffer[512];
memset(temp_buffer, ’\0’, 512);
char *p_temp_buffer = temp_buffer;
char ch = fgetc(fp);

  定义了一个read_state的枚举,在定义枚举的时候一般第一个成员定义成default,表示一种无效或者默认的状态,c里的枚举不能用xxx.yyy来访问,只能用yyy来访问,跟常量一样,所以我们定义成员的时候加上一个state_前缀,这样在使用的时候就知道是个枚举了。

  下面还定义了要返回的account_record_list
result,因为在栈上声明的变量没人给初始化,所以result.count我们要人工设置为0,p_record是指向result.records的指针,它是一个指向数组的指针,这样可以用p_record++来依次对每个记录赋值,而不需要像用下标访问那样得知道下标值,再一个就是指针可以提高一点性能。

  temp_buffer是定义的一个临时缓冲区,因为我们解析输入文件,肯定要对原文件进行一些分隔等,所以要用临时缓存区保存临时结果。同理,这里生成的字符数组也没人给初始化,我们用memset来把每个字节都初始化成’\0’。最后也用一个p_temp_buffer指针来指向临时缓冲区,指针我们就以p_做前缀,这样能看出来。

  接下来是对输入文件的解析,我们要尽量保证函数的短小,所以这里的逻辑只是按分隔符找出每个字段,具体每个字段的解析又调用了各个set_xxx的函数。

网赌平台哪个信誉好 11网赌平台哪个信誉好 12代码

while (ch != EOF)
{
   if(result.count > MAX_RECORD_COUNT)
   {
       printf("max record count");
       break;
   }
   if(ch != '|' && ch != '\n'){
       *(p_temp_buffer++) = ch;
   }
   else{
       *(p_temp_buffer++) = '\0';
       switch(state)
       {
           case state_date:
               set_date(p_record,temp_buffer);
               state = state_consumption;
               break;
           case state_consumption:
               set_consumption(p_record,temp_buffer);
               state = state_discount;
               break;
           case state_discount:
               set_discount(p_record,temp_buffer);
               state = state_person;
               break;
           case state_person:
               set_person(p_record,temp_buffer);
               state = state_payrecord;
               break;
           case state_payrecord:
               set_payrecord(p_record,temp_buffer);
               state = state_default;
               break;

           default:
               printf("state is error");
               break;
       }
       memset(temp_buffer, 0, 512);
       p_temp_buffer = temp_buffer;

   }
   if(ch == '\n'){
       result.count++;
       p_record++;
       memset(temp_buffer, 0, 512);
       p_temp_buffer = temp_buffer;
       state = state_date;
   }
   putchar(ch);
   ch = fgetc(fp);
}
fclose(fp);
return result;         

  这些逻辑性的东西就没什么说的了,逐个读取每个字符,如果遇到分隔符|或者\n就把这段字符放入缓冲区,并传给set_xxx来处理,注意每次set_xxx后要重置缓冲区的内容,以及让缓冲区指针指向起始位置。这里读取完某个字段后要把读取状态修改成下一个状态,这也是简单的状态机的应用,在字符串解析方面用的很广。

  最后记着要fclose文件,否则会资源泄漏,像那些成对出现的api要时刻记着配平资源,比如foepn,fclose,malloc,free这种,少半拉的话,一般就会引起资源泄漏问题。  

  我们在看一个set_xxx方法,对付款记录的解析是最复杂的,我们就看这个,付款记录字段格式是先用逗号分隔每个人的付款记录,再用冒号分隔付款人和付款金额。在c里有个strtok的函数,类似split,可以把一个字符串分隔成多个子串,这里也用到了临时缓冲区,把传入的只读字符串用strncpy拷贝到临时缓冲区里再做处理,strncpy比strcpy安全,因为后者拷贝时会一直拷贝,直到遇到\0为止,前者可以指定最多拷贝多少个字符。

网赌平台哪个信誉好 13网赌平台哪个信誉好 14代码

void set_payrecord(struct account_record *record, const char *buff){
    char temp_buffer[512];
    memset(temp_buffer, 0, 512);
    strncpy(temp_buffer, buff, 512*sizeof(char));

    char c[MAX_ARRAY_COUNT][2*MAX_ARRAY_COUNT] = {{'\0'}};
    char (*pc)[2*MAX_ARRAY_COUNT] = c;

    char *p = strtok(temp_buffer,",");
    int paycount= 0;
    while(p != NULL)
    {
        strncpy(*pc++, p, 2*MAX_ARRAY_COUNT*sizeof(char));
        p = strtok(NULL,",");
        paycount++;
    }

    struct pay_record *payrecord = record -> payrecord;
    int i = 0;
    for(i = 0; i < paycount; i++)
    {
        char *p2 = strtok(c[i],":");
        if(p2 == NULL)
        {
            printf("error:parse payrecord error");
            return;
        }
        struct people person;
        strncpy(person.name, p2, MAX_ARRAY_COUNT*sizeof(char));
        p2 = strtok(NULL,":");
        if(p2 == NULL)
        {
            printf("error:parse payrecord error");
            return;
        }
        double amount = atof(p2);

        payrecord -> person = person;
        payrecord -> amount = amount;
        payrecord++;
        record -> pay_record_count++;
    }
}

  这里需要一个两维数组,声明两维数组就用char [3][4]
就行,c99里只是声明数组时直接初始化,用={{‘\0’}}就可以把数组都初始化成’\0’,然后虽然这是一个两位的数组,但要用一维的数组指针去指,如char
(*pc)[4],然后用*pc就能访问二维数组的每一行了,每一行是个字符数组,可以用strncpy等函数操作。注意strtok不能嵌套使用,所以先用它把逗号分隔的子串放入到二维数组里,然后便利二维数组的每一行,对每一行按冒号分隔取出付款人和付款金额,最后放到内存对象里。

1. 链表

链表是linux内核中最简单,同时也是应用最广泛的数据结构。

内核中定义的是双向链表。

 

处理记账记录

  这个模块比较小,edit_person_consumption用来处理每一笔消费和付款记录,先看list里有没有这个人,如果有这个人就直接把金额修改掉,如果没有,就在list里添加一个人机器消费记录。这里有个问题折腾了半天,就是我把strcmp写成strcpy了,编译也没问题,但输出结果让人很诡异,赋值都乱了,看来这种编译不出错,运行时给个错误值的问题是最难排查的,拼写错误真是程序员最常见的错误呀。剩下两个函数比较简单,打印没人余额记录和计算人均消费。

 

网赌平台哪个信誉好 15网赌平台哪个信誉好 16代码

#include "data_structure.h"

void edit_person_consumption(struct person_consumption_list *list,
        const char *name,double money)
{
    int i = 0;
    int found = -1;
    for(i = 0; i < list -> count; i++)
    {
        if(strcmp(list -> persons[i].name, name) == 0)
        {
            found = i;
            list -> persons[i].consumption += money;
        }
    }

    if(found == -1)
    {
        int count = list -> count;
        strncpy(list -> persons[count].name, name, MAX_ARRAY_COUNT);
        list -> persons[count].consumption = money;
        list -> count++;
    }
}
void print_person_consumption_list(const struct person_consumption_list list)
{
    int i;
    printf("\n-----consumption details-------\n");
    for(i = 0; i < list.count; i++)
    {
        printf("%s=%0.2f\n",list.persons[i].name,list.persons[i].consumption);
    }
}
double calc_avg_consumption(double total,int person_count,double discount)
{
    return total * discount / person_count;
}

 

1.1 头文件简介

内核中关于链表定义的代码位于: include/linux/list.h

list.h文件中对每个函数都有注释,这里就不详细说了。

其实刚开始只要先了解一个常用的链表操作(追加,删除,遍历)的实现方法,

其他方法基本都是基于这些常用操作的。

 

编译及测试

  上篇帖子简单介绍过makefile的编写,以下是该程序的makefile文件,注意换行符和跳格键的使用。

网赌平台哪个信誉好 17网赌平台哪个信誉好 18代码

 book:readinput.o record_handler.o \
     data_structure.h readinput.h record_handler.h\
     main.c
     gcc main.c -o book readinput.o record_handler.o
 readinput.o: data_structure.h readinput.h readinput.c
     gcc -c readinput.c
 record_handler: data_structure.h record_handler.h record_handler.c
     gcc -c record_handler.c

  最后输出一个book的可执行文件,执行./book,输出以下结果,符合预期

  可以看到d负的最多,因为它吃了两顿都没付钱,下次吃饭就该他出钱了,而b正的最多,可以连续一周不用付款吃饭了。

 

网赌平台哪个信誉好 19网赌平台哪个信誉好 20代码

2010-9-10|83|0.8|a,b,c,d|a:100,b:100
2010-9-11|102|0.8|a,b,c,d,e|b:100,c:50
2010-9-10
        discount=0.80
        consumption=83
        person:
                a,b,c,d,
        pay_record
                a:100.00
                b:100.00

2010-9-11
        discount=0.80
        consumption=102
        person:
                a,b,c,d,e,
        pay_record
                b:100.00
                c:50.00


-----consumption details-------
a=67.08
b=167.08
c=17.08
d=-32.92
e=-16.32

 

1.2 链表代码的注意点

在阅读list.h文件之前,有一点必须注意:linux内核中的链表使用方法和一般数据结构中定义的链表是有所不同的。

一般的双向链表一般是如下的结构,

  • 有个单独的头结点(head)
  • 每个节点(node)除了包含必要的数据之外,还有2个指针(pre,next)
  • pre指针指向前一个节点(node),next指针指向后一个节点(node)
  • 头结点(head)的pre指针指向链表的最后一个节点
  • 最后一个节点的next指针指向头结点(head)

具体见下图:

网赌平台哪个信誉好 21

 

传统的链表有个最大的缺点就是不好共通化,因为每个node中的data1,data2等等都是不确定的(无论是个数还是类型)。

linux中的链表巧妙的解决了这个问题,linux的链表不是将用户数据保存在链表节点中,而是将链表节点保存在用户数据中。

linux的链表节点只有2个指针(pre和next),这样的话,链表的节点将独立于用户数据之外,便于实现链表的共同操作。

 

具体见下图:

网赌平台哪个信誉好 22

 

linux链表中的最大问题是怎样通过链表的节点来取得用户数据?

和传统的链表不同,linux的链表节点(node)中没有包含用户的用户data1,data2等。

 

整个list.h文件中,我觉得最复杂的代码就是获取用户数据的宏定义

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

这个宏没什么特别的,主要是container_of这个宏

#define container_of(ptr, type, member) ({          \
    const typeof(((type *)0)->member)*__mptr = (ptr);    \
             (type *)((char *)__mptr - offsetof(type, member)); })

这里面的type一般是个结构体,也就是包含用户数据和链表节点的结构体。

ptr是指向type中链表节点的指针

member则是type中定义链表节点是用的名字

比如:

struct student
{
    int id;
    char* name;
    struct list_head list;
};
  • type是struct student
  • ptr是指向stuct list的指针,也就是指向member类型的指针
  • member就是 list

下面分析一下container_of宏:

// 步骤1:将数字0强制转型为type*,然后取得其中的member元素
((type *)0)->member  // 相当于((struct student *)0)->list

// 步骤2:定义一个临时变量__mptr,并将其也指向ptr所指向的链表节点
const typeof(((type *)0)->member)*__mptr = (ptr);

// 步骤3:计算member字段距离type中第一个字段的距离,也就是type地址和member地址之间的差
// offset(type, member)也是一个宏,定义如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

// 步骤4:将__mptr的地址 - type地址和member地址之间的差
// 其实也就是获取type的地址

步骤1,2,4比较容易理解,下面的图以sturct student为例进行说明步骤3:

首先需要知道 ((TYPE *)0) 表示将地址0转换为 TYPE 类型的地址

由于TYPE的地址是0,所以((TYPE *)0)->MEMBER 也就是
MEMBER的地址和TYPE地址的差,如下图所示:

网赌平台哪个信誉好 23

 

小节

  其实最终的每人余额可以从小到大排个序,可以练习一下冒泡排序和函数指针的使用,不过这也算是一个比较有意义的下程序了,多写代码,C的入门也就快了。下次可能给大家分享下如何配置VIM能更快的编写C程序,工具的熟练程度会大大影响开发效率。

  语言,工具等在编程里都是次要矛盾,编程的主要要解决的问题是业务逻辑本身的复杂性,所以要经常写一些逻辑比较复杂的小程序来提高编程能力,可以迅速提高思维能力,减少出错的能力,在写代码的过程中所犯的错误都积累起来,以后就可以一次编写,直接执行就通过了,编译和运行都没有错误,推荐下我前段时间写的练习作品:大家来找错-自己写个正则引擎

源码下载:bookkeeper.zip

环境:cygwin+gcc3.4.4+vim7.3.3+make3.8.1


1.3 使用示例

构造了一个内核模块来实际使用一下内核中的链表,代码在CentOS6.3
x64上运行通过。

C代码:

#include<linux/init.h>
#include<linux/slab.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/list.h>

MODULE_LICENSE("Dual BSD/GPL");
struct student
{
    int id;
    char* name;
    struct list_head list;
};

void print_student(struct student*);

static int testlist_init(void)
{
    struct student *stu1, *stu2, *stu3, *stu4;
    struct student *stu;

    // init a list head
    LIST_HEAD(stu_head);

    // init four list nodes
    stu1 = kmalloc(sizeof(*stu1), GFP_KERNEL);
    stu1->id = 1;
    stu1->name = "wyb";
    INIT_LIST_HEAD(&stu1->list);

    stu2 = kmalloc(sizeof(*stu2), GFP_KERNEL);
    stu2->id = 2;
    stu2->name = "wyb2";
    INIT_LIST_HEAD(&stu2->list);

    stu3 = kmalloc(sizeof(*stu3), GFP_KERNEL);
    stu3->id = 3;
    stu3->name = "wyb3";
    INIT_LIST_HEAD(&stu3->list);

    stu4 = kmalloc(sizeof(*stu4), GFP_KERNEL);
    stu4->id = 4;
    stu4->name = "wyb4";
    INIT_LIST_HEAD(&stu4->list);

    // add the four nodes to head
    list_add (&stu1->list, &stu_head);
    list_add (&stu2->list, &stu_head);
    list_add (&stu3->list, &stu_head);
    list_add (&stu4->list, &stu_head);

    // print each student from 4 to 1
    list_for_each_entry(stu, &stu_head, list)
    {
        print_student(stu);
    }
    // print each student from 1 to 4
    list_for_each_entry_reverse(stu, &stu_head, list)
    {
        print_student(stu);
    }

    // delete a entry stu2
    list_del(&stu2->list);
    list_for_each_entry(stu, &stu_head, list)
    {
        print_student(stu);
    }

    // replace stu3 with stu2
    list_replace(&stu3->list, &stu2->list);
    list_for_each_entry(stu, &stu_head, list)
    {
        print_student(stu);
    }

    return 0;
}

static void testlist_exit(void)
{
    printk(KERN_ALERT "*************************\n");
    printk(KERN_ALERT "testlist is exited!\n");
    printk(KERN_ALERT "*************************\n");
}

void print_student(struct student *stu)
{
    printk (KERN_ALERT "======================\n");
    printk (KERN_ALERT "id  =%d\n", stu->id);
    printk (KERN_ALERT "name=%s\n", stu->name);
    printk (KERN_ALERT "======================\n");
}

module_init(testlist_init);
module_exit(testlist_exit);

Makefile:

obj-m += testlist.o

#generate the path
CURRENT_PATH:=$(shell pwd)
#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)
#the absolute path
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complie object
all:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
    rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned
#clean
clean:
    rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned

安装,卸载内核模块以及查看内核模块的运行结果:

insmod testlist.ko
rmmod testlist
dmesg | tail -100

 

2. 队列

内核中的队列是以字节形式保存数据的,所以获取数据的时候,需要知道数据的大小。

如果从队列中取得数据时指定的大小不对的话,取得数据会不完整或过大。

 

2.1 头文件简介

内核中关于队列定义的头文件位于:<linux/kfifo.h>
include/linux/kfifo.h

头文件中定义的函数的实现位于:kernel/kfifo.c

 

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图