ubuntu下2.6.21內核的驅動開發實例

 globalvar.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <asm/semaphore.h>
MODULE_LICENSE("GPL");

#define MAJOR_NUM 253

static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);
static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);

static struct file_operations globalvar_fops =
{
read: globalvar_read, write: globalvar_write,
};

static int global_var = 0;
static struct semaphore sem;
static wait_queue_head_t outq;
static int flag = 0;

static int __init globalvar_init(void)
{
int ret;
ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
if (ret)
{
printk("globalvar register failure");
}
else
{
printk("globalvar register success");
init_MUTEX(&sem);
init_waitqueue_head(&outq);
}
return ret;
}

static void __exit globalvar_exit(void)
{
int ret;
ret = unregister_chrdev(MAJOR_NUM, "globalvar");
if (ret)
{
printk("globalvar unregister failure");
}
else
{
printk("globalvar unregister success");
}
}

static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
//等待數據可獲得
if(wait_event_interruptible(outq, flag != 0))
{
return - ERESTARTSYS;
}

if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}

flag = 0;
if (copy_to_user(buf, &global_var, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
up(&sem);
return sizeof(int);
}

static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len,loff_t *off)
{
if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}
if (copy_from_user(&global_var, buf, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
up(&sem);
flag = 1;
//通知數據可獲得
wake_up_interruptible(&outq);
return sizeof(int);
}
module_init(globalvar_init);
module_exit(globalvar_exit);

Makefile:

obj-m += globalvar.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

make後生成.ko文件,insmod加載該模塊,然後在/dev/目錄下建立設備節點:mknod globalvar c 253 0


測試應用程序
read.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
main()
{
int fd, num;
fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != - 1)
{
while (1)
{
read(fd, &num, sizeof(int)); //程序將阻塞在此語句,除非有針對globalvar的輸入
printf("The globalvar is %d/n", num);
//如果輸入是0,則退出
if (num == 0)
{
close(fd);
break;
}
}
}
else
{
printf("device open failure/n");
}
close(fd);
}

write.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
main()
{
int fd, num;
fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != - 1)
{
while (1)
{
printf("Please input the globalvar:/n");
scanf("%d", &num);
write(fd, &num, sizeof(int));
//如果輸入0,退出
if (num == 0)
{
close(fd);
break;
}
}
}
else
{
printf("device open failure/n");
}
close(fd);
}

關於主設備號和次設備號
linux下的設備在/dev/目錄下一般會有相對應的節點,關於設備號有如下的言論:
主設備號被系統用來確定驅動程式,次設備號被驅動程式用來確定具體的設備。

像如下的兩個字符設備節點:
crw-r--r--  1 root root 254, 0  Jan  9 13:14 /dev/nx_ids
crw-r--r--  1 root root 254, 99 Jan  9 13:14 /dev/nx_ips

他們的主設備號都是254,當用戶代碼打開這兩個設備的時候,系統會定位到同一個驅動程式,並調用其open函數。那麼驅動程式怎麼知道用戶打開的是哪一個設備呢?就是根據次設備號來判斷的。

int my_drv_open(struct inode *inode, struct file *filp)
{
    if (0 == MINOR(inode->i_rdev))
    {
        //nx_ids
    }
    else if (99 == MINOR(inode->i_rdev))
    {
        //nx_ips
    }
    return 0;
}
在include/linux/kdev_t.h文檔中能夠看到操作設備號的方法
***關於中斷
* 申請中斷應該在設備打開的時候進行,而不要在模塊初始化的時候進行,因爲中斷信號線非常有限,如果在模塊初始化的時候申請了中斷,那麼驅動程序可能只是佔用而從未使用,也會阻止起其他驅動使用該中斷,而在設備打開的時候申請中斷,則可以共享中斷。
request_irq()、free_irq()
  
  這是驅動程序申請中斷和釋放中斷的調用。在include/linux/sched.h裏聲明。request_irq()調用的定義:
  int request_irq(unsigned int irq,
  void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
  unsigned long irqflags,
  const char * devname,
  void *dev_id);
  
   irq是要申請的硬件中斷號。在Intel平臺,範圍0--15。handler是向系統登記的中斷處理函數。這是一個回調函數,中斷髮生時,系統調用 這個函數,傳入的參數包括硬件中斷號,device id,寄存器值。dev_id就是下面的request_irq時傳遞給系統的參數dev_id。irqflags是中斷處理的一些屬性。比較重要的有 SA_INTERRUPT,標明中斷處理程序是快速處理程序(設置SA_INTERRUPT)還是慢速處理程序(不設置SA_INTERRUPT)。快速 處理程序被調用時屏蔽所有中斷。慢速處理程序不屏蔽。還有一個SA_SHIRQ屬性,設置了以後運行多個設備共享中斷。dev_id在中斷共享時會用到。 一般設置爲這個設備的device結構本身或者NULL。中斷處理程序可以用dev_id找到相應的控制這個中斷的設備,或者用rq2dev_map找到 中斷對應的設備。
    void free_irq(unsigned int irq,void *dev_id);

*驅動中,如果要休眠,必須釋放該設備的信號量或自旋鎖。

***時鐘
  
  時鐘的處理類似中斷,也是登記一個時間處理函數,在預定的時間過後,系統時鐘的處理類似中斷,也是登記一個時間處理函數,在預定的時間過後,系統會調用這個函數。在include/linux/timer.h裏聲明。
  
  struct timer_list {
  struct timer_list *next;
  struct timer_list *prev;
  unsigned long expires;
  unsigned long data;
  void (*function)(unsigned long);
  };
  void add_timer(struct timer_list * timer);
  int del_timer(struct timer_list * timer);
  void init_timer(struct timer_list * timer);
  
   使用時鐘,先聲明一個timer_list結構,調用init_timer對它進行初始化。time_list結構裏expires是標明這個時鐘的周 期,單位採用jiffies的單位。jiffies是Linux一個全局變量,代表時間。它的單位隨硬件平臺的不同而不同。系統裏定義了一個常數HZ,代 表每秒種最小時間間隔的數目。這樣jiffies的單位就是1/HZ。Intel平臺jiffies的單位是1/100秒,這就是系統所能分辨的最小時間 間隔了。所以expires/HZ就是以秒爲單位的這個時鐘的週期。 中國網管聯盟bitsCN.com
  
  function就是時間到了以後的回調函數,它的參數就是timer_list中的data。data這個參數在初始化時鐘的時候賦值,一般賦給它設備的device結構指針。
  
  在預置時間到系統調用function,同時系統把這個time_list從定時隊列裏清除。所以如果需要一直使用定時函數,要在function裏再次調用add_timer()把這個函數。
發佈了28 篇原創文章 · 獲贊 2 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章