Char类型自然是Cprogramming中最为常用的数据类型之一,我们都认为再简单不过了,所以使用的时候会如果信手拈来可能会产生不必要的麻烦,甚至是严重的bug,这类错误往往又是比较难debug的。
笔者当前使用的LinuxKernel是2.6.27.8,我们先看该版本下JFFS2 file system源代码中的一个例子。这里为了方便理解,列出其源代码:
static int jffs2_garbage_collect_metadata(structjffs2_sb_info *c, struct jffs2_eraseblock *jeb,
structjffs2_inode_info *f, struct jffs2_full_dnode *fn)
{
structjffs2_full_dnode *new_fn;
structjffs2_raw_inode ri;
structjffs2_node_frag *last_frag;
unionjffs2_device_node dev;
char *mdata = NULL, mdatalen = 0;
uint32_talloclen, ilen;
int ret;
if(S_ISBLK(JFFS2_F_I_MODE(f)) ||
S_ISCHR(JFFS2_F_I_MODE(f)) ) {
/*For these, we don't actually need to read the old node */
mdatalen= jffs2_encode_dev(&dev, JFFS2_F_I_RDEV(f));
mdata= (char *)&dev;
D1(printk(KERN_DEBUG"jffs2_garbage_collect_metadata(): Writing %d bytes of kdev_t\n",mdatalen));
} else if(S_ISLNK(JFFS2_F_I_MODE(f))) {
mdatalen = fn->size;
mdata= kmalloc(fn->size, GFP_KERNEL);
if(!mdata) {
printk(KERN_WARNING"kmalloc of mdata failed in jffs2_garbage_collect_metadata()\n");
return-ENOMEM;
}
ret= jffs2_read_dnode(c, f, fn, mdata, 0, mdatalen);
if(ret) {
printk(KERN_WARNING"read of old metadata failed in jffs2_garbage_collect_metadata():%d\n", ret);
kfree(mdata);
returnret;
}
D1(printk(KERN_DEBUG"jffs2_garbage_collect_metadata(): Writing %d bites of symlinktarget\n", mdatalen));
}
ret = jffs2_reserve_space_gc(c, sizeof(ri) + mdatalen,&alloclen,
JFFS2_SUMMARY_INODE_SIZE);
if (ret) {
printk(KERN_WARNING"jffs2_reserve_space_gc of %zd bytes for garbage_collect_metadata failed:%d\n",
sizeof(ri)+ mdatalen, ret);
gotoout;
}
last_frag =frag_last(&f->fragtree);
if(last_frag)
/*Fetch the inode length from the fragtree rather then
* from i_size since i_size may have not beenupdated yet */
ilen= last_frag->ofs + last_frag->size;
else
ilen= JFFS2_F_I_SIZE(f);
memset(&ri,0, sizeof(ri));
ri.magic =cpu_to_je16(JFFS2_MAGIC_BITMASK);
ri.nodetype= cpu_to_je16(JFFS2_NODETYPE_INODE);
ri.totlen =cpu_to_je32(sizeof(ri) + mdatalen);
ri.hdr_crc= cpu_to_je32(crc32(0, &ri, sizeof(struct jffs2_unknown_node)-4));
ri.ino =cpu_to_je32(f->inocache->ino);
ri.version= cpu_to_je32(++f->highest_version);
ri.mode =cpu_to_jemode(JFFS2_F_I_MODE(f));
ri.uid =cpu_to_je16(JFFS2_F_I_UID(f));
ri.gid =cpu_to_je16(JFFS2_F_I_GID(f));
ri.isize =cpu_to_je32(ilen);
ri.atime =cpu_to_je32(JFFS2_F_I_ATIME(f));
ri.ctime =cpu_to_je32(JFFS2_F_I_CTIME(f));
ri.mtime =cpu_to_je32(JFFS2_F_I_MTIME(f));
ri.offset =cpu_to_je32(0);
ri.csize =cpu_to_je32(mdatalen);
ri.dsize =cpu_to_je32(mdatalen);
ri.compr =JFFS2_COMPR_NONE;
ri.node_crc= cpu_to_je32(crc32(0, &ri, sizeof(ri)-8));
ri.data_crc= cpu_to_je32(crc32(0, mdata, mdatalen));
new_fn =jffs2_write_dnode(c, f, &ri, mdata, mdatalen, ALLOC_GC);
if(IS_ERR(new_fn)) {
printk(KERN_WARNING"Error writing new dnode: %ld\n", PTR_ERR(new_fn));
ret= PTR_ERR(new_fn);
gotoout;
}
jffs2_mark_node_obsolete(c,fn->raw);
jffs2_free_full_dnode(fn);
f->metadata= new_fn;
out:
if(S_ISLNK(JFFS2_F_I_MODE(f)))
kfree(mdata);
return ret;
}
这个函数的目的就是garbage collect那些只有meta data的files,这些文件通常有:character device file、block device file、symbol link file。JFFS2定义meta data的最大长度为254。我们这里不去讨论file system的结构、所以我们也不去解释为什么是254基本上也就够了。
首先这里我们看到定义: char *mdata = NULL, mdatalen = 0;
mdatalen是meat data的长度,这里定义为char。
讲到这里我们也许就已经知道答案了。char只有8 bits,如果char缺省是signed char的话,它的范围为[-128, 127)。
这样:mdatalen = fn->size;和ret =jffs2_reserve_space_gc(c, sizeof(ri) + mdatalen, &alloclen, JFFS2_SUMMARY_INODE_SIZE);就会有问题:当fn->size >=128&& fn->size < 255时,mdatalen其实就是一个负数。然而jffs2_reserve_space_gc函数的函数原型为:intjffs2_reserve_space_gc(struct jffs2_sb_info *c, uint32_t minsize, uint32_t*len, uint32_t sumsize)。
第二个参数是unsigned int minsize,这样相当于传入了一个非常大的数。
这个问题现在看来似乎很简单,而且奇怪的是JFFS2的设计者为什么会犯这样的错误?
其实这里面还有一些东西值得探讨,通常我们要定义无符号的整数需要加unsigned关键字,如:unsigned int/long/short等,如果没有unsigned的,就是有符号整数,也就是说int/long/short缺省为有符号。然后对于char就不是这么简单了。其实这种缺省取决于我们用的编译器和平台,ARMGCC对char做了相反的缺省定义,当我们定义char ch;时ch是一个unsigned char。为什么会这样定义我没有去细想,如果有人知道请留言告诉我。
这样来说,之前的代码应该没有问题才对呀?但是这里面还是至少有两个问题:
1. 会有跨平台移植的问题,这个比较简单,不多说。
2. 即便是ARM平台下,我们也是可以通过-fsigned-char编译参数设定char的缺省模式为signed char。笔者现在正在使用LTIB就定义了-fsigned-char。
这里我们也可以看出许多公司定义coding rule的意义。在实践中我们通常都定义:
typedef unsigned char uint8_t;
typedef signed char int8_t;
之后我们就不要直接使用char了。
这里还有一个例子,来自LinuxKernel 3.7.6:drivers\tty\serial\max3100.c:
static void max3100_work(struct work_struct *w)
{
structmax3100_port *s = container_of(w, struct max3100_port, work);
intrxchars;
u16 tx, rx;
int conf,cconf, rts, crts;
struct circ_buf *xmit = &s->port.state->xmit;
dev_dbg(&s->spi->dev,"%s\n", __func__);
rxchars =0;
do {
spin_lock(&s->conf_lock);
conf= s->conf;
cconf= s->conf_commit;
s->conf_commit= 0;
rts= s->rts;
crts= s->rts_commit;
s->rts_commit= 0;
spin_unlock(&s->conf_lock);
if(cconf)
max3100_sr(s,MAX3100_WC | conf, &rx);
if(crts) {
max3100_sr(s,MAX3100_WD | MAX3100_TE |
(s->rts ? MAX3100_RTS : 0), &rx);
rxchars+= max3100_handlerx(s, rx);
}
max3100_sr(s,MAX3100_RD, &rx);
rxchars+= max3100_handlerx(s, rx);
if(rx & MAX3100_T) {
tx= 0xffff;
if(s->port.x_char) {
tx= s->port.x_char;
s->port.icount.tx++;
s->port.x_char= 0;
}else if (!uart_circ_empty(xmit) &&
!uart_tx_stopped(&s->port)) {
tx= xmit->buf[xmit->tail];
xmit->tail= (xmit->tail + 1) &
(UART_XMIT_SIZE- 1);
s->port.icount.tx++;
}
if(tx != 0xffff) {
max3100_calc_parity(s,&tx);
tx|= MAX3100_WD | (s->rts ? MAX3100_RTS : 0);
max3100_sr(s,tx, &rx);
rxchars+= max3100_handlerx(s, rx);
}
}
if(rxchars > 16 && s->port.state->port.tty != NULL) {
tty_flip_buffer_push(s->port.state->port.tty);
rxchars= 0;
}
if(uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(&s->port);
} while(!s->force_end_work &&
!freezing(current) &&
((rx & MAX3100_R) ||
(!uart_circ_empty(xmit) &&
!uart_tx_stopped(&s->port))));
if (rxchars> 0 && s->port.state->port.tty != NULL)
tty_flip_buffer_push(s->port.state->port.tty);
}
请看红色部分,简单说明一下tx = xmit->buf[xmit->tail];
这里tx定义为u16,而xmit->buf的定义是char:
struct circ_buf {
char *buf;
int head;
int tail;
};
其结果为:在ARM平台下,如果我们定义了-fsigned-char,那么这个驱动就再也无法发送0xFF了,因为当xmit->buf[[xmit->tail]为0xff时,tx就被赋值为0xffff, 这样max3100_sr(s, tx, &rx);就会fail而无法调用max3100_sr(s, tx, &rx);去发送0xff了。