Posts Tagged ‘character’

字符驱动poll函数与select()函数的交互

September 25th, 2015

在字符驱动中,我们经常要实现poll()的功能,具体实现在注册到file_operations 的函数中,举个例子

static unsigned int xxx_poll(struct file *file, poll_table *wait)
{
         poll_wait(file, &mp_chrdev_wait, wait);
         if (rcu_access_index(mplog.next))
                 return POLLIN | POLLRDNORM;
 
         return 0;
}

我们必须在这个函数中返回POLLIN、POLLOUT等状态,从而我们可以在用户态下使用FD_ISSET()判断数据是否到来。而其中void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait);它的作用就是把当前进程添加到wait参数指定的等待列表(poll_table)中。需要注意的是这个函数是不会引起阻塞的。

这里我们实现创建了一个mp_chrdev_wait的等待队列,它会把这个轮训进程放入一个等待队列中,然后这个进程会睡眠(表现在select()上就是阻塞)。当某个条件满足时,唤醒这个等待队列,也就是唤醒了轮训进程,也就是内核通知应用程序(应用程序的select函数会感知),这个时候mask返回值中有数据。然后就会接着select操作。所以我们要在恰当的位置wake_up_interruptible(&mp_chrdev_wait)

在用户空间中的代码,我们需要使用select()轮训在这个设备上:

          int register_fd, ret;
          fd_set rds;
  
          register_fd = open(CONFIG_PATH, O_RDWR);
          if (register_fd < 0)
                 err("opening of /dev/mplog");
 
          FD_ZERO(&rds);
          FD_SET(register_fd,&rds);
  
  
          while (1) {
                  /*
                   * Proceed with the rest of the daemon.
                  */
                 memset(temp, 0, MP_LOG_LEN * sizeof(struct mp));
  
 
                  ret = select(register_fd+1,&rds,NULL,NULL,NULL);
                  if(ret < 0 )
                  {
                          close(register_fd);
                          err("select error!");
                  }
                  if(FD_ISSET(register_fd,&rds))
                         read(register_fd, temp, MP_LOG_LEN * sizeof(struct mp));
 .... 
          }

字符编码浅析

March 26th, 2015

最近在linux与windows上面做切换,经常会遇到乱码问题,又联想起很多因为字符解析失败,最后debug出来是因为字符编码的问题后,准备仔细研究这一块奇怪的东东。

我主要是使用汉语,所以与我们息息相关的编码时gb2312、GBK和UTF系列。

GB2312是最早的汉语标准,但是最大的问题是收录的汉字太少,导致许多复杂的函数无法在机器中表示。之后Microsoft使用GB2312未使用的编码空间,拓展了收录的字符数。

但是真正的国家标准是GB18030,它收录了7万多个汉字,目前的国内软件都必须支持这个字符集。

我们都知道ASCII码在电脑中用1 byte来表示,比如 strlen(“l123”),就返回一个字符长度4,而汉字在电脑中使用2 byte来表示,也就是strlen(“李123”)返回5!

如果在一个页面中存在多种语言,那在我们看来就是一堆乱码!所以unicode出现了,但是unicode又会分成很多字符标准:

  • utf-16 的实现是每个字符有2 byte来表示,但是这种编码方式没有运用在浏览器中。

在windows中使用宽字符进行表示,也是说下面代码虽然有汉字,但是每个s元素都是2 byte,最后的len也还是4!

wchar_t s[10];
int len=0;

wcscpy(s, L“李123”);
len = wcslen(s);

wchar_t 其实是 unsigned short,一个 16-bit,C 语言所有的 strxxx 都有相对应的 wcsxxx,字符串前面加上 L,代表宽字符。

  • utf-16 的实现是每个字符由4 byte来表示,太占空间,没有人使用。
  • utf-8是我们现在通用的一种字符表示编码,每个字符是可变长度的。

每个字可能是 1~6个bytes (2003年后删减剩下 1~4 个 bytes)

  1. US-ASCII (0 ~ 127) : 1-byte
  2. 部分各国字母: 2-byte (例如希腊字母,西里尔字母…)
  3. 其它常用字 : 3-byte (大部分的汉字)
  4. 极少用字 : 4~6-byte (包含罕见汉字 ,麻将牌…)

也就是说平时的英文数字大概一个字符一个byte,而汉字 3 byte。

unicode上面这幅图表示的是utf8的表示方式[1],我们可以看到电脑在寻找这个字符由几个byte主要是看byte 1,有几个1 就有几个byte。

strlen("李123")

上面的代码使用unicode的话就会返回6!下面来分析下大小端与字符编码的关系:

我们都知道小端就是low byte在前,high byte在后,典型使用这种方式就是x86架构。

  • 在GB2312中 李123 = e6 9d 8e 31 32 33,那么他在小端机器中,文档中打开也是这个样子:e6 9d 8e 31 32 33
  • 在utf-16中 李123 = 67 4e  00 31 00 32 00 33 在文档中打开:4e 67 31 00 32 00 33 00。因为他是以两个byte为一个单位。
  • 在utf-8中 李123=e6 9d 8e 31 32 33 在文档中打开也是:e6 9d 8e 31 32 33

所以基于上述知识,我们知道在notepad打开一个文档,有时候会读取BOM!BOM 位于文档的开头位置

  • FE FF 开头 ->UTF-16 Big Endian
  • FF FE 开头 ->UTF-16 Little Endian
  • EF BB BF 开头 -> UTF-8
  • 都不是,看系统默认编码 (GB2312?)

所以我们在代码读取文档的时候,要小心BOM的存在,BOM 可以告诉你后续内容的编码方式,有时候必须跳过 BOM,不要把 BOM 当成内容一起处理。

在Linux 中解决代码转换问题:iconv,而一些特定的语言例如python使用 str.encode(‘utf-8’)。

I18N:所有的语言都写在外部txt,方便做本地适配,代码也不用重新编译。

 

[1] http://en.wikipedia.org/wiki/UTF-8

实践字符设备驱动

October 7th, 2014

我们都知道在linux里面存在块设备与字符设备。我们这里是设计的字符驱动,在不久,我会加入支持阻塞的功能。

我们可以通过查看cat /proc/device查看已注册的设备

[lzz@localhost device_character]$ cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 14 sound
 21 sg
 29 fb
 99 ppdev
116 alsa
128 ptm
136 pts
162 raw
180 usb
188 ttyUSB
189 usb_device
202 cpu/msr
203 cpu/cpuid
226 drm
250 hidraw
251 usbmon
252 bsg
253 watchdog
254 rtc

Block devices:
259 blkext
  8 sd
  9 md
 11 sr
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
253 device-mapper
254 mdp

我们选取一个231作为我们字符设备号,别的不废话,上代码.

字符设备驱动程序:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <linux/mman.h>
#include <linux/uaccess.h>

MODULE_AUTHOR("lzz");
MODULE_LICENSE("GPL");

#define MYCDEV_MAJOR 231 /*the predefined mycdev's major devno*/
#define MYCDEV_SIZE 100

static char kernel_buf[MYCDEV_SIZE];

static int mycdev_open(struct inode *inode, struct file *fp)
{
 return 0;
}

static int mycdev_release(struct inode *inode, struct file *fp)
{
 return 0;
}

static ssize_t mycdev_read(struct file *fp, char __user *buf, size_t size, loff_t *pos)
{

 unsigned long p = *pos;
 unsigned int count = size;
// int i;

 if(p >= MYCDEV_SIZE)
 return -1;
 if(count > MYCDEV_SIZE)
 count = MYCDEV_SIZE - p;

 if (copy_to_user(buf, kernel_buf, count) != 0) 
 {
 printk("read error!\n");
 return -1;
 }

 printk("lzz's reader: %d bytes was read...\n", count);
 return count;

}

static ssize_t mycdev_write(struct file *fp, const char __user *buf, size_t size, loff_t *pos)
{
 unsigned long p = *pos;
 unsigned int count = size;
 int ret =0;

 if(copy_from_user(kernel_buf+p,buf,count))
 ret = -EFAULT;
 else
 {
 *pos+=count;
 ret = count;
 printk("lzz's write: %d bytes was wroten...\n", count);
 }
 return ret;
}

/*filling the mycdev's file operation interface in the struct file_operations*/
static const struct file_operations mycdev_fops =
{
 .owner = THIS_MODULE,
 .read = mycdev_read,
 .write = mycdev_write,
 .open = mycdev_open,
 .release = mycdev_release,
};

/*module loading function*/
static int __init mycdev_init(void)
{
 int ret;

 printk("mycdev module is staring..\n");

 ret=register_chrdev(MYCDEV_MAJOR,"lzz_cdev",&mycdev_fops);
 if(ret<0)
 {
 printk("register failed..\n");
 return 0;
 }
 else
 {
 printk("register success..\n");
 } 

 return 0;
}

/*module unloading function*/
static void __exit mycdev_exit(void)
{
 printk("mycdev module is leaving..\n");
 unregister_chrdev(MYCDEV_MAJOR,"lzz_cdev");
}

module_init(mycdev_init);
module_exit(mycdev_exit);

Makefile文件:

obj-m:= map_driver.o

CURRENT_PATH:=$(shell pwd)

LINUX_KERNEL:=$(shell uname -r)

LINUX_KERNEL_PATH:=/lib/modules/$(LINUX_KERNEL)/build

all:
 make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules

clean:
 make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

用户态测试程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main()
{
 int testdev;
 int i, ret;
 char buf[100]="test lzz's cdev ok!";
 char buff[100];

 testdev = open("/dev/mycdev", O_RDWR);

 if (-1 == testdev) {
 printf("cannot open file.\n");
 exit(1);
 }

 write(testdev,buf,sizeof(buf)-1);

 if ((ret = read(testdev, buff, sizeof(buff)-1)) < 0) {
 printf("read error!\n");
 exit(1);
 }

 printf("%s\n", buff);

 close(testdev);

 return 0;
}

使用方法:
1.make编译map_driver.c文件,并插入到内核;
2.通过cat /proc/devices 查看系统中未使用的字符设备主设备号,比如当前231未使用;
3.创建设备文件结点:sudo mknod /dev/mycdev c 231 0;具体使用方法通过man mknod命令查看;
4.修改设备文件权限:sudo chmod 777 /dev/mycdev;(可选)
5.以上成功完成后,编译本用户态测试程序;运行该程序查看结果;
6.通过dmesg查看日志信息;

Screenshot from 2014-10-07 22:10:11