存档

‘Linux内核实践’ 分类的存档

Linux 2.6内核下实现一个操作/proc的内核模块

2009年5月18日 1 条评论

Linux课程实践的一次作业,要求在linux下构建一个在/proc中实现clock文件的模块,该文件支持读操作。
然后开始疯狂的google操作。进而发现整个流程应该是这样的
如果是ubuntu或者fedora发行版,需要配置对应的内核开发环境,而SUSE Enterprise则不用。我用的是ubuntu,在这里就简要的介绍一下ubuntu下内核开发环境的配置。
首先需要安装源build-essential这个包,对应的命令为

sudo apt-get install build-essential

然后再去下载自己的版本所对应的Linux源代码
查看自己的Linux版本的命令为

uname -a

我自己的显示是2.6.27-14-generic
所以应该去下载2.6.27的Linux源代码,命令为:

sudo apt-get install linux-source-2.6.27

好,等这个包安装完成以后,我们进入/usr/src这个目录下将其解压缩,因为默认是把这个文件下载到这个目录下的,你也可以拷贝这个文件到任何你想要放置的位置进行解压
解压完成以后,进入该目录,进行编译操作,命令如下
Step 1:

make mrproper

一下,这个动作是将你使用的源代码包恢复到初始状态
Step 2:
配置内核
保存原有系统配置文件,将/boot/下的config-2.6.27-14-generic拷贝到/usr/src/linux-source-2.6.27(刚下载下来解压的Linux源码下,并把名字改为.config)
对应命令为

sudo cp /boot/config-2.6.27-14-generic /usr/src/linux-source-2.6.27
sudo mv config-2.6.27-14-generic .config

接着进行内核的配置

sudo apt-get install libncurses5-dev

(make menuconfig要调用的)

make menuconfig

当你make menuconfig后,选倒数第二项:Load an alternate configuration file
把.config加载进来,这样你就能在原来内核的基础之上修改了
Step3:

make

让他编译吧,耐心等待,我笔记本性能比较差,要编译两个小时左右的时间
Step4:

make bzImage

Step5:

make modules

Step6:

make modules_install

Step7:

make install

重新启动
至此,内核树就建立啦,我们就可以进行相应的内核模块的开发了
贴上自己的clock.c文件,该文件读取内核时间,然后在/proc目录下创建一个clock文件,在clock文件里打印出系统自1970年1月1日起的秒数和纳秒数,中间用空格隔开

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#define PROCFS_NAME	"clock"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("conan");
 
static struct proc_dir_entry *clock_proc_file;
 
//called when a read is done on /proc/my_clock
int read_time(char *buf,char **start,off_t offset,int count,int *eof,void *data){
 
  struct timespec now;
  int len = 0;
  if (offset > 0)
  {
    printk(KERN_INFO "offset %d : /proc/%s : read_time, \
       wrote %d Bytes\n", (int)(offset),PROCFS_NAME, len);
    *eof = 1;
    return len;
  }
  now = current_kernel_time();
  len = sprintf(buf, "%ld %ld\n", now.tv_sec, now.tv_nsec);
  printk(KERN_INFO "now time is %s\n",buf);
  return len;
}
 
 
//called when module is inserted in system
int init_module(){
  int ret_value = 0;
  clock_proc_file=create_proc_entry(PROCFS_NAME,0644,NULL);
  clock_proc_file->read_proc = read_time;
  printk(KERN_INFO "Trying to create /proc/clock:\n");
  if (clock_proc_file == NULL)
  {
    ret_value = -ENOMEM;
    printk(KERN_INFO "Error: Could not initialize /proc/%s\n",PROCFS_NAME);
  }
  else
  {
    printk(KERN_INFO "Initialize /proc/%s\n",PROCFS_NAME);
  }
  return ret_value;
}
//called to remove module from system
void cleanup_module(){
  remove_proc_entry(PROCFS_NAME,NULL);
  printk(KERN_INFO "/proc/%s removed\n",PROCFS_NAME);
}

接着是这个文件的Makefile
obj-m := clock.o
KDIR = /usr/src/linux-source-2.6.27/
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -f *.mod.c *.mod.o *.ko *.o *.tmp_versions
编译的时候千万要注意Makefile和clock.c(不要改名,除非对应的Makefile也改)一定要在一个名字中没有空格的文件夹中,否则就会出现没有找到对应规则的错误!我搞了近两个小时才发现了这个问题。
编译完成之后使用命令insmod clock.ko装载该模块,这个时候就可以
cat /var/log/syslog查看相应的输出信息了,如果模块被正确装载,则会在这个文件的底部出现相应的调试信息。
接着,再是我的测试程序,这个程序对/proc/clock进行了读取,并将读取的值与gettimeofday获取的值作比较。
代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#define MAX 10
#define LENGTH 21
 
int main(){
   int i;
   FILE *my_clock;
   struct timeval gtodTimes[MAX];
   char procClockTimes[MAX][LENGTH];
 
   printf("Please wait for a while, test is running...\n");
   //getting times from both module and system call
   for(i=0;i<MAX;i++){
      my_clock = fopen("/proc/clock", "r");
      if (my_clock == NULL)
      {
         printf("/proc/clock doesn't exist\n");
         exit(1);
      }
      else
      {
         gettimeofday(&gtodTimes[i], NULL);
         fgets(procClockTimes[i], LENGTH, my_clock);
         fclose(my_clock);
         sleep(1);
      }
   }
   //printing data
   for(i=0;i<MAX;i++){
      char *nsec_pointer;
      char *delim = " ";
      nsec_pointer = strtok(procClockTimes[i],delim);
      nsec_pointer = strtok(NULL,delim);
      printf("At iteration %d, Gettimeofday=%lds %ldus, myClock=%ss %sns\n", i,
             gtodTimes[i].tv_sec, gtodTimes[i].tv_usec ,procClockTimes[i],nsec_pointer);
   }
   return(0);
}

Linux VIRTT定时器设置时间与初始时间不一致的问题

2009年5月13日 没有评论

首先,来看一段测试程序,该程序设定了一个virtual 的定时器,然后用getitimer()得到当前的定时器的值
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
struct itimerval virtt;
virtt.it_interval.tv_sec = 1;
virtt.it_interval.tv_usec = 0;
virtt.it_value.tv_sec = 1;
virtt.it_value.tv_usec = 0;
setitimer(ITIMER_VIRTUAL, &virtt, NULL);

while (1)
{
getitimer(ITIMER_VIRTUAL,&virtt);
printf("tv_sec %ld\n",virtt.it_value.tv_sec);
printf("tv_usec %ld\n",virtt.it_value.tv_usec);
}
return 0;
}

运行程序得到测试结果如下:
tv_sec 1
tv_usec 4062
tv_sec 1
tv_usec 4062
tv_sec 1
tv_usec 4062

//好多好多重复
tv_sec 1
tv_usec 62
tv_sec 1
tv_usec 62
tv_sec 1
tv_usec 62

//好多好多重复
tv_sec 0
tv_usec 996062
tv_sec 0
tv_usec 996062
tv_sec 0
tv_usec 996062
tv_sec 0
tv_usec 996062
//…
//下面继续递减4000us(一个jiffy的值)
OK,为什么明明设置的是1s的定时器,怎么变成了1.004062s了,这多出来的是4062哪来的?让我们看一下Linux的源代码
首先找到do_setitimer这个函数,setitimer调用的就是这个函数,贴个代码片段:
int do_setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
{
struct task_struct *tsk = current;
struct hrtimer *timer;
ktime_t expires;
cputime_t cval, cinterval, nval, ninterval;

/*
* Validate the timevals in value.
*/
if (!timeval_valid(&value->it_value) ||
!timeval_valid(&value->it_interval))
return -EINVAL;

switch (which) {
case ITIMER_REAL:
...
break;
case ITIMER_VIRTUAL:
nval = timeval_to_cputime(&value->it_value);//使用我们自己设置的定时器的数值,返回一个long赋值给nval
ninterval = timeval_to_cputime(&value->it_interval);
spin_lock_irq(&tsk->sighand->siglock);
cval = tsk->signal->it_virt_expires;
cinterval = tsk->signal->it_virt_incr;
if (!cputime_eq(cval, cputime_zero) ||//如果nval或者cval的值不为0
!cputime_eq(nval, cputime_zero)) {
if (cputime_gt(nval, cputime_zero))//如果nval的值大于0,则给nval加上一个jiffy的时间,这就解释了为什么一开始设置的0一下子多了4000us,那还有多出来的62us是哪来的,让我们再来看一下上面的timeval_to_cputime函数是干什么的,请转到下一段代码片段
nval = cputime_add(nval,
jiffies_to_cputime(1));
set_process_cpu_timer(tsk, CPUCLOCK_VIRT,//开始设置进程的计时器,并将其挂到相应的队列
&nval, &cval);
}
tsk->signal->it_virt_expires = nval;
tsk->signal->it_virt_incr = ninterval;
spin_unlock_irq(&tsk->sighand->siglock);
if (ovalue) {
cputime_to_timeval(cval, &ovalue->it_value);
cputime_to_timeval(cinterval, &ovalue->it_interval);
}
break;
case ITIMER_PROF:
...
default:
return -EINVAL;
}
return 0;
}

下面是timeval_to_cputime的代码片段:

unsigned long sec = value->tv_sec;
long usec = value->tv_usec;

if (sec >= MAX_SEC_IN_JIFFIES){
sec = MAX_SEC_IN_JIFFIES;
usec = 0;
}
return (((u64)sec * SEC_CONVERSION) +
(((u64)usec * USEC_CONVERSION + USEC_ROUND) >>//这里有一个复杂的运算操作,这个值就是上面的nval,具体的宏定义以及这段代码的意思可以去看源码上面的说明
(USEC_JIFFIE_SC - SEC_JIFFIE_SC))) >> SEC_JIFFIE_SC;

紧接着,我们再来研究一下do_getitimer,和do_setitimer相反,这个函数通过调用cputime_to_timeval函数将当前定时器的值赋值给应用程序的timeval结构,下面是代码片段:

u32 rem;

value->tv_sec = div_u64_rem((u64)jiffies * TICK_NSEC,
NSEC_PER_SEC, &rem);
value->tv_usec = rem / NSEC_PER_USEC;//这里有一个除法操作,看到这里我猜多出来的62us就是因为上面的复杂运算和这个除法操作共同导致的

OK,现在我们已经从实现上了解了为什么我们设置的定时器一开始的时候与设置的值不同,那么,为什么Linux要这么设计呢?
贴一段因为英文说明:
Implementations may place limitations on the timer value. To make sure that a process gets at least as much time as requested, the timer value is rounded up to the next timer tick (a timer tick is the smallest supported value).
The timer value is rounded up to the next timer tick because, the timer will be initialize somewhere between timer ticks.
If a setitimer() is followed by a getitimer() without a timer tick in between, it is possible that the value returned by getitimer() may be more than the initial value requested by setitimer() due to this rounding.
简单的来说,就是Linux想让一个进程的计时开始时间一定要大于等于我们自己设置的时间。