2015年5月6日 星期三

[轉] 在Android 裡讓 TextView 可以 scroll (ScrollView)

http://cw1057.blogspot.tw/2011/11/android-textview-scrollscrollview.html

[轉] 使用變數型別的良好習慣

最近在把老人寫的code 在x86_64 上做測試

才深刻體會原來寫一個好程式 要注意 data type 的使用


在32bit 機器上
#include <stdio.h>
int main()
{
        int i = 10;
        int *p = &i;
        int pp = (int)p;
        return 0;
}


gcc 不會warning 

但同樣程式在64bit 機器上跑
cast.c:7:20: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
           int pp = (int)p;


這是warning 理論上你可以不用管他
但是這很危險 可能會產生出很多莫名其妙的bug  ~~
所以編譯程式最好加上 -Werror  
把warning 都當成error  處理
迫使你寫的程式沒有額外的風險

回到正題
在64 bit上要解決這個warning or error
要改成

#include <stdio.h>
int main()
{
          int i = 10;
          int *p = &i;
          long pp = (long)p;
          return 0;
}



為什麼?  因為32bit 跟64bit 機器上pointer 長度不同啊
一個是32 bit 一個是64 bit  
所以把64bit 資料強迫轉型成32 bit integer 當然要警告你 可能會lost data 

當然你可以下面這種方式來解決
if machine32
  int pp = (int)p;
elif machine64
 long pp = (long)p;
endif


但這太麻煩了, stdint.h 內定義了 intptr_t 這個type
當你需要把pointer 轉型成 integer 的時候 你不確定是32 or 64bit
就可以使用intptr_t

#include <stdio.h>
#include <stdint.h>
int main()
{
        int i = 10;
        int *p = &i;
        intptr_t pp = (intptr_t)p;
        return 0;
}


所以 下面這三個打印數值是相同的
#include <stdio.h>
#include <stdint.h>
int main()
{
          int i = 10;
          int *p = &i;
          intptr_t pp = (intptr_t)p;
          printf("%p\n", p);
          printf("%lx\n", pp);
          printf("%p\n", &i);
          return 0;
}

 來看看stdint.h 怎樣定義intptr_t

/* Types for `void *' pointers.  */
#if __WORDSIZE == 64
# ifndef __intptr_t_defined
typedef long int                intptr_t;
#  define __intptr_t_defined
# endif
typedef unsigned long int       uintptr_t;
#else
# ifndef __intptr_t_defined
typedef int                     intptr_t;
#  define __intptr_t_defined
# endif
typedef unsigned int            uintptr_t;
#endif
   

偵測到是64bit 
intptr_t 就是long int  ( 64位元的long int 是64bit, 但32位元的long int是32bit)
否則就是 int   ( 64位元的 int 是32bit, 32位元的int 也是32bit)

================================

同樣的, 關於portability 還有幾點需要介紹
那就是primitive data type
                    32            64
char             1               1
short            2               2
int                4               4
long             4               8
long long     8               8
pointer         4               8

可以注意到 long, and pointer type 是有差異的
所以long 必不portable
所以stdint.h 內也定義了

 35 #ifndef __int8_t_defined
 36 # define __int8_t_defined
 37 typedef signed char             int8_t;
 38 typedef short int               int16_t;
 39 typedef int                     int32_t;
 40 # if __WORDSIZE == 64
 41 typedef long int                int64_t;
 42 # else
 43 __extension__
 44 typedef long long int           int64_t;
 45 # endif
 46 #endif
 47

如果你明確要使用64位元的整數 請使用int64_t
可以看到int64_t 在64bit machine 是long int
而32bit machine 要用long long int

================= size_t =================
看看下面這個小程式

  1 #include <stdio.h>
  2 #include <string.h>
  3
  4 void main()
  5 {
  6         int i = -1;
  7         if (i > strlen ("hello"))
  8                 printf("hello\n");
  9 }

-1 > 5 ?  
yes, 確實會印出hello
這裡的問題是 i 是signed , 用來跟strlen 傳回的size_t (unsigned ) 做比較
會把signed i 轉unsigned 
(unsigned) (-1) 會大於 5

再回來review code
當我們要知道元素大小的時候 我們可以確定至少是0以上
所以應該要用unsigned 來表示
但我們卻常常為了方便而使用int

所以size_t 定義成unsigned integer 
而且是平台相關的
在64bit machine  size_t 是unsigned long
但在32bit machine size_t 是unsigned int


==================ptrdiff_t ================
同樣的 用來表示pointer 之間的difference
但是是signed integer
64bit machine => long
32bit machine => int

[轉] #pragma pack(1)表示的意義

http://white5168.blogspot.tw/2013/04/pragma-pack1.html#.VUsFwI6qqko

2015年5月5日 星期二

[轉] ioctl,unlocked_ioctl和compat_ioctl

kernel 2.6.35 及之前的版本中struct file_operations 一共有3個ioctl :
ioctl
unlocked_ioctl
compat_ioctl
現在只有unlocked_ioctl和compat_ioctl 了

在kernel 2.6.36 中已經完全刪除了struct file_operations 中的ioctl 函數指針,取而代之的是unlocked_ioctl。

這個指針函數變了之後最大的影響是參數中少了inode,不過這個不是問題。
因為用戶程序中的ioctl對應的系統調用接口沒有變化,所以用戶程序不需要改變,一切都交給內核處理了。

如果想在 unlocked_ioctl 中獲得 inode 等信息可以用如下方法:
struct inode *inode = file->f_mapping->host;
struct block_device *bdev = inode->i_bdev;
struct gendisk *disk = bdev->bd_disk;
fmode_t mode = file->f_mode;
struct backing_dev_info *bdi;

這次內核函數的變化引出了一個問題,從ioctl系統調用往後,真正的ioctl調用順序是什麼?為什麼compat_ioctl 不被調用?
compat_ioctl被使用在用戶空間為32位模式,而內核運行在64位模式時。這時候,需要將64位轉成32位。
以下是2.6.36的情況:
SYSCALL_DEFINE3(ioctl ...) compat_sys_ioctl (是否直接調用compat_ioctl 取決於compat_ioctl 是否存在) 
| | |-----> compat_ioctl
| | |------>do_vfs_ioctl (下一步的調用取決於file->f_path.dentry->d_inode->i_node) 
| |------>file_ioctl
| | |---------------------->vfs_ioctl
|------->unlock_ioctl

其實compat_ioctl 沒有被調用的原因是compat_sys_ioctl 沒有被調用,
而它沒有被調用的原因似乎是壓根就沒有編譯到內核中,因為我沒有找到調用這個函數的代碼。
unlocked_ioctl 實際上取代了用了很久的ioctl,主要的改進就是不再需要上大內核鎖(調用之前不再先調用lock_kernel()然後再unlock_kernel())
總的來說kernel 開發者正在試圖朝移除大內核鎖的方向努力,ioctl的移除就是被革命了。
相信以後越來越多的內核函數會擺脫大內核鎖的依賴,並且大內核鎖最終會被移除。

轉載自:http://lp007819.wordpress.com/2011/01/06/kernel-2-6-36-ioctl-%E5%8F%98%E6%9B%B4/一、內核原型(linux2.6.28-7
     long (*compat_ioctl)(struct tty_struct *tty, struct file * file,
                     unsigned int cmd, unsigned long arg);

     implement ioctl processing for 32 bit process on 64 bit system
     Optional

二、What is compat_ioctl
Consider a scenario where application is 32 bit and kernel as well as architecture is 64 bit. In this case, when an application calls ioctl(), there should be some mechanism in kernel to handle 32 bit to 64 bit conversion. This conversion is especially required when user passes objects of type "long" and "void *".
There is one more method called as "compat_ioctl()" that a 64 bit driver has to implement. It gets called when 64 bit kernel gets ioctl() call from 32 bit user.
Tasks to be done by compat_ioctl() :
1. Acquire BKL, since kernel calls compat_ioctl without BKL.
2. 32 to 64 bit conversion for long and pointer objects passed by user
3. Process input data, get results.
4. 64 to 32 bit conversion in order to pass the output data back to user
5. Release BKL

三、中文檔案
Linux 64Bit 下的 ioctlcompat_ioctl ioctl32 Unknown cmd fd
前段時間將我們的程式移植到Mips64Linux 2.6環境下,做可行性試驗。
由於使用者態程程式規模太大,而且之前沒有對64bit的情況做考慮,
所以,使用者態程式任然使用32位元模式編譯,內核運行在64bit
我們有一個內核模組之前是在2.4.21下的,拿到2.6後,把部分api做了些更改,直接編譯並載入。
使用者態程式在調用ioctl的時候,總是失敗。
dmesg
看一下內核資訊,有如下類似資訊:
ioctl32(add_vopp:9767): Unknown cmd fd(3) cmd(80048f00){00} arg(ff240ae0)
後來在內核中的ioctl中添加debug代碼,發現根本沒調用到內核中的ioctl函數。
經過查找,發現了以下資源
The new way of ioctl()
32 bit user/64 bit kernel
What is compat_ioctl ()
more on compat_ioctl
產生問題的分析:
我們的程式通過Linux
ssize_t read(int fd, void *buf, size_t count); 
系統調用從虛擬裝置中讀取內核中。
使用
int ioctl(int fd, int request, ...);
系統調用來對設備進行控制,並傳遞一些資料。
ioctl
是我們擴展非標準系統調用的手段。


read
系統調用,對應內核中struct file_operations 結構中的
ssize_t (*read) (struct file *filp, char *buf, size_t count, loff_t *f_pos)
當用戶態位32bit, 內核位64bit的時候,
使用者態的程式,和內核態的程式,在資料類型不一致。
比如指針,用戶態的指針實際上是unsigned long 4Byte內核態的指針是unsigned ong: 8Byte.對於這種不一致,從用戶態陷入內核態的時候,大部分標準調用的參數已經做了相應的轉化。
不存在問題。比如ssize_t,內核態和用戶態不一致,對於這種已知類型,內核代碼已經做了轉換,因為他知道該怎麼轉,
所以我們的程式調用readwrite,等系統調用,都能正確的返回結果。
再來看看ioctl系統調用,
使用者態系統調用,int ioctl(int fd, int request, ...);內核中對應的函數
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
request
是請求號,用來區分不同得到請求。後面的可變參數隨便選。
如果想傳入或傳出自訂結構,可以傳個指標類型。
如果沒資料傳遞,可以空著。
當然也可以傳這個intchar之類的類型來向內核傳遞資料。
問題來了,內核不知道你傳遞的是那種類型,他無法給你轉化。
總結一下:
1). Linux2.6
64bit kernel struct file_operation中增加了一個成員
long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
用來提供32位元使用者態程式的方法,
可以通過HAVE_COMPAT_IOCTL宏來判斷該成員是否存在,並提供相應的操作方法。
2). 
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)相比
compat_ioctl
少了inode參數可以通過filp->f_dentry->d_inode方法獲得。
3). 關於ioctl的請求號,經常是通過巨集定義生成的 舉例如下:
#define IoctlGetNetDeviceInfo    _IOWR(Ioctl_Magic, 1, struct_NetDeviceInfo)
#define IoctlNetQueueInit          _IOR(Ioctl_Magic, 4, struct_NetDeviceListen)
#define IoctlNetQueueDestroy     _IO(Ioctl_Magic, 5)
注意_IOWRIOR他們的最後一個參數是一個資料類型,展開時會包含sizeof()操作,
於是32bit使用者態程式和64bit內核之間,生成的ioctlrequest號很可能就會不同,
compat函數中switch()的時候,就會switch不到。
要想辦法避免:
提供64bit32bit大小一致的結構。
在使用者態下提供一個偽結構,偽結構和內核內的結構寬度一致。
_IO巨集,而不用_IOWR_IOR宏,反正只是為了得到一個號碼,實際傳輸資料大小,自己心理有數,內核代碼處理好就行了。
4). 
如果compat收到的最後參數arg是一個用戶態指標它在用戶態是32位的,在內核中為了保證安全,
可以使用compat_ptr(art)宏將其安全的轉化為一個64位的指標(仍然是用戶指標)


問題:

ioctl32: Unknown cmd

如果遇到這個錯誤 ... 都是在64/32轉換的問題
在你的 driver 加上 compat_ioctl .. 他會自己轉換到 compat_ioctl去