记录锁相当于线程同步中读写锁的一种扩展类型,可以用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,通过fcntl函数来执行上锁操作。尽管读写锁也可以通过在共享内存区来进行进程的同步,但是fcntl记录上锁往往更容易使用,且效率更高。

记录锁的功能:当一个进程正在读或修改文件的某个部分是,它可以阻止其他进程修改同一文件区。对于这个功能阐述我认为有三点要解释的:

  • 记录锁不仅仅可以用来同步不同进程对同一文件的操作,还可以通过对同一文件加记录锁,来同步不同进程对某一共享资源的访问,如共享内存,I/O设备。
  • 对于劝告性上锁,当一个进程通过上锁对文件进行操作时,它不能阻止另一个非协作进程对该文件的修改。
  • 即使是强制性上锁,也不能完全保证该文件不会被另一个进程修改。因为强制性锁对unlink函数没有影响,所以一个进程可以先删除该文件,然后再将修改后的内容保存为同一文件来实现修改。具体可参考《APUE》P367

1记录锁函数接口

记录上锁的POSIX接口函数fcntl如下:

  1. /* Do the file control operation described by CMD on FD.
  2. The remaining arguments are interpreted depending on CMD. */
  3. int fcntl (int __fd, int __cmd, ...);
  4.  
  5. //根据cmd的不同有以下三种类型的调用
  6. int fcntl(int fd, int cmd);
  7. int fcntl(int fd, int cmd, long arg);
  8. int fcntl(int fd, int cmd, struct flock *lock);

由函数名称可知fcntl的功能是对文件的控制操作,根据传入不同的操作类型命令cmd,fcntl会执行不同的操作,fcnt根据cmd不同,接收可变的参数。具体有以下五种类型的操作:

  1. /*
  2. cmd = F_DUPFD,复制一个文件描述符;
  3. */
  4. int fcntl(int fd, int cmd);
  5.  
  6. /*
  7. cmd = F_GETFD,获得文件描述符标志;
  8. cmd = F_SETFD,设置文件描述符标志;arg = 描述符标志的值,目前只定义了一个标志: FD_CLOEXEC
  9. int fcntl(int fd, int cmd);
  10. int fcntl(int fd, int cmd, long arg);
  11. */
  12.  
  13. /*
  14. cmd = F_GETFL,获得文件状态标志;
  15. cmd = F_SETFL,设置文件状态标志;arg = 状态标志的值
  16. int fcntl(int fd, int cmd);
  17. int fcntl(int fd, int cmd, long arg);
  18. */
  19.  
  20. /*
  21. cmd = F_GETOWN,获得当前接收SIGIO和SIGURG信号的进程ID或进程组ID
  22. cmd = F_SETOWN,设置接收SIGIO和SIGURG信号的进程ID或进程组ID;arg = 进程ID或进程组ID
  23. int fcntl(int fd, int cmd);
  24. int fcntl(int fd, int cmd, long arg);
  25. */
  26.  
  27. /*
  28. Return value:
  29.  
  30. 对于成功的调用,根据操作类型cmd不同,有以下几种情况:
  31. F_DUPFD 返回新的文件描述符
  32. F_GETFD 返回文件描述符标志
  33. F_GETFL 返回文件状态标志
  34. F_GETOWN 进程ID或进程组ID
  35. All other commands 返回0
  36. 调用失败, 返回-1,并设置errno。
  37. */

上面四个功能都是fcntl提供的很常用的操作,关于记录锁的功能就是fcntl提供的第五个功能,具体使用如下:

  1. int fcntl(int fd, int cmd, struct flock *lock);
  2.  
  3. /*
  4. cmd = F_GETLK,测试能否建立一把锁
  5. cmd = F_SETLK,设置锁
  6. cmd = F_SETLKW, 阻塞设置一把锁
  7.  
  8. */
  9. //POSIX只定义fock结构中必须有以下的数据成员,具体实现可以增加
  10. struct flock {
  11. short l_type; /* 锁的类型: F_RDLCK, F_WRLCK, F_UNLCK */
  12. short l_whence; /* 加锁的起始位置:SEEK_SET, SEEK_CUR, SEEK_END */
  13. off_t l_start; /* 加锁的起始偏移,相对于l_whence */
  14. off_t l_len; /* 上锁的字节数*/
  15. pid_t l_pid; /* 已经占用锁的PID(只对F_GETLK 命令有效) */
  16. /*...*/
  17. };
  18. //Return value: 前面已经说明;

l F_SETLK:获取(l_type为F_RDLCK或F_WRLCK)或释放由lock指向flock结构所描述的锁,如果无法获取锁时,该函数会立即返回一个EACCESS或EAGAIN错误,而不会阻塞。

l F_SETLKW:F_SETLKW和F_SETLK的区别是,无法设置锁的时候,调用线程会阻塞到该锁能够授权位置。

l F_GETLK:IF_GETLK主要用来检测是否有某个已存在锁会妨碍将新锁授予调用进程,如果没有这样的锁,lock所指向的flock结构的l_type成员就会被置成F_UNLCK,否则已存在的锁的信息将会写入lock所指向的flock结构中

这里需要注意的是,用F_GETLK测试能否建立一把锁,然后接着用F_SETLK或F_SETLKW企图建立一把锁,由于这两者不是一个原子操作,所以不能保证两次fcntl之间不会有另外一个进程插入并建立一把相关的锁,从而使一开始的测试情况无效。所以一般不希望上锁时阻塞,会直接通过调用F_SETLK,并对返回结果进行测试,以判断是否成功建立所要求的锁。

2记录锁规则说明

前面我们说了记录锁相当于读写锁的一种扩展类型,记录锁和读写锁一样也有两种锁:共享读锁(F_RDLCK)和独占写锁(F_WRLCK)。在使用规则上和读写锁也基本一样:

  • 文件给定字节区间,多个进程可以有一把共享读锁,即允许多个进程以读模式访问该字节区;
  • 文件给定字节区间,只能有一个进程有一把独占写锁,即只允许有一个进程已写模式访问该字节区;
  • 文件给定字节区间,如果有一把或多把读锁,不能在该字节区再加写锁,同样,如果有一把写锁,不能再该字节区再加任何读写锁。

如下表所示:

需要说明的是:上面所阐述的规则只适用于不同进程提出的锁请求,并不适用于单个进程提出的多个锁请求。即如果一个进程对一个文件区间已经有了一把锁,后来该进程又试图在同一文件区间再加一把锁,那么新锁将会覆盖老锁。

下面进行测试;第一个程序是在同一进程中测试能否在加写锁后,继续加读写锁。第二个程序是在在父进程中加写锁后,然后再子进程中测试能否继续加读写锁。

  1. //调用的函数,在文章末尾贴出
  2. int main()
  3. {
  4. int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
  5. writew_lock(fd);
  6.  
  7. cout<<lock_test(fd, F_WRLCK, SEEK_SET, 0, 0)<<endl;
  8. cout<<lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)<<endl;
  9.  
  10. unlock(fd);
  11.  
  12. return 0;
  13. }

执行结果为:

  1. 0
  2. 0

表明同一进程可以对已加锁的同一文件区间,仍然能获得加锁权限;

  1. //调用的函数,在文章末尾贴出
  2. int main()
  3. {
  4. int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
  5. writew_lock(fd);
  6.  
  7. if (fork() == 0)
  8. {
  9. cout<<lock_test(fd, F_WRLCK, SEEK_SET, 0, 0)<<endl;
  10. cout<<lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)<<endl;
  11.  
  12. exit(0);
  13. }
  14.  
  15. sleep(3);
  16. unlock(fd);
  17.  
  18. return 0;
  19. }

执行结果为:

  1. 24791
  2. 24791

表明不同进程不能对已加写锁的同一文件区间,获得加锁权限;

还有就是:加锁时,该进程必须对该文件有相应的文件访问权限,即加读锁,该文件必须是读打开,加写锁时,该文件必须是写打开。

3记录锁的粒度

这里要提到两个概念:记录上锁和文件上锁

记录上锁:对于UNIX系统而言,“记录”这一词是一种误用,因为UNIX系统内核根本没有使用文件记录这种概念,更适合的术语应该是字节范围锁,因为它锁住的只是文件的一个区域。用粒度来表示被锁住文件的字节数目。对于记录上锁,粒度最大是整个文件。

文件上锁:是记录上锁的一种特殊情况,即记录上锁的粒度是整个文件的大小。

之所以有文件上锁的概念是因为有些UNIX系统支持对整个文件上锁,但没有给文件内的字节范围上锁的能力。

4记录锁的隐含继承与释放

关于记录锁的继承和释放有三条规则,如下:

(1)锁与进程和文件两方面有关,体现在:

  • 当一个进程终止时,它所建立的记录锁将全部释放;
  • 当关闭一个文件描述符时,则进程通过该文件描述符引用的该文件上的任何一把锁都将被释放。

对于第一个方面,可以建立如下测试代码:

  1. //调用的函数,在文章末尾贴出
  2.  
  3. //process 1
  4. int main()
  5. {
  6. int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
  7.  
  8. writew_lock(fd);
  9. cout<<"process 1 get write lock..."<<endl;
  10.  
  11. sleep(10);
  12.  
  13. cout<<"process 1 exit..."<<endl;
  14. return 0;
  15. }
  16.  
  17. //process 2
  18. int main()
  19. {
  20. int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
  21.  
  22. writew_lock(fd);
  23. cout<<"process 2 get write lock..."<<endl;
  24. unlock(fd);
  25.  
  26. return 0;
  27. }

先启动进程1,然后立即启动进程2,执行结果如下:

  1. process 1 get write lock...
  2. process 1 exit...
  3. process 2 get write lock...

对于第二个方面,可以进行如下测试:

  1. //调用的函数,在文章末尾贴出
  2. int main()
  3. {
  4. int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
  5.  
  6. if (fork() == 0)
  7. {
  8. int fd_1 = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
  9.  
  10. readw_lock(fd_1);
  11. cout<<"child get read lock..."<<endl;
  12.  
  13. sleep(3);
  14.  
  15. close(fd_1);
  16. cout<<"close the file descriptor..."<<endl;
  17.  
  18. pause();
  19. }
  20.  
  21. sleep(1);
  22.  
  23. writew_lock(fd);
  24. cout<<"parent get write lock..."<<endl;
  25. unlock(fd);
  26.  
  27. return 0;
  28. }

程序的执行结果如下:

  1. child get read lock...
  2. close the file descriptor...
  3. parent get write lock...

可见,当关闭文件描述符时,与该文件描述符有关的锁都被释放,同样通过dup拷贝得到的文件描述符也会导致这种情况;

(2)由fork产生的子进程不继承父进程所设置的锁。即对于父进程建立的锁而言,子进程被视为另一个进程。记录锁本身就是用来同步不同进程对同一文件区进行操作,如果子进程继承了父进程的锁,那么父子进程就可以同时对同一文件区进行操作,这有违记录锁的规则,所以存在这么一条规则。

下面是测试代码(上面已经用过该代码进行测试):

  1. //调用的函数,在文章末尾贴出
  2. int main()
  3. {
  4. int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
  5. writew_lock(fd);
  6.  
  7. if (fork() == 0)
  8. {
  9. cout<<lock_test(fd, F_WRLCK, SEEK_SET, 0, 0)<<endl;
  10. cout<<lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)<<endl;
  11.  
  12. exit(0);
  13. }
  14.  
  15. sleep(3);
  16. unlock(fd);
  17.  
  18. return 0;
  19.  
  20. }

我们知道在前面已经说过,同一个进程可以重复对同一个文件区间加锁,后加的锁将覆盖前面加的锁。那么再假设如果子进程继承了父进程的锁,那么子进程可以对该锁进行覆盖,那么在子进程内对该锁是否能获得权限的测试应该是可以,但测试结果为:

  1. 24791
  2. 24791

表明已经进程24791已经占用该锁,所以假设不成立,子进程不会继承父进程的锁;

(3)执行exec后,新程序可以继承原执行程序的锁。但是,如果一个文件描述符设置了close-on-exec标志,在执行exec时,会关闭该文件描述符,所以对应的锁也就被释放了,也就无所谓继承了。

5记录锁的读和写的优先级

在读写锁中,我曾经测试过Linux 2.6.18中提供的读写锁函数是优先考虑等待读模式占用锁的线程,这种实现的一个很大缺陷就是出现写入线程饿死的情况 那么在记录锁中是什么样的规则呢,需要说明的是这在POSIX标准中是没有说明的,要看具体实现。

具体进行以下2个方面测试:

  1. 进程拥有读出锁,然后写入锁等待期间额外的读出锁处理;
  2. 进程拥有写入锁,那么等待的写入锁和等待的读出锁的优先级;

测试1:父进程获得对文件的读锁,然后子进程1请求加写锁,随即进入睡眠,然后子进程2请求读锁,看进程2是否能够获得读锁。

  1. //调用的函数,在文章末尾贴出
  2. int main()
  3. {
  4. int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
  5. readw_lock(fd);
  6.  
  7. //child 1
  8. if (fork() == 0)
  9. {
  10. cout<<"child 1 try to get write lock..."<<endl;
  11. writew_lock(fd);
  12. cout<<"child 1 get write lock..."<<endl;
  13.  
  14. unlock(fd);
  15. cout<<"child 1 release write lock..."<<endl;
  16.  
  17. exit(0);
  18. }
  19.  
  20. //child 2
  21. if (fork() == 0)
  22. {
  23. sleep(3);
  24.  
  25. cout<<"child 2 try to get read lock..."<<endl;
  26. readw_lock(fd);
  27. cout<<"child 2 get read lock..."<<endl;
  28.  
  29. unlock(fd);
  30. cout<<"child 2 release read lock..."<<endl;
  31. exit(0);
  32. }
  33.  
  34. sleep(10);
  35. unlock(fd);
  36.  
  37. return 0;
  38. }

在Linux 2.6.18下执行结果如下:

  1. child 1 try to get write lock...
  2. child 2 try to get read lock...
  3. child 2 get read lock...
  4. child 2 release read lock...
  5. child 1 get write lock...
  6. child 1 release write lock...

可知在有写入进程等待的情况下,对于读出进程的请求,系统会一直给予的。那么这也就可能导致写入进程饿死的局面。

测试2:父进程获得写入锁,然后子进程1和子进程2分别请求获得写入锁和读写锁,看两者的响应顺序;

  1. //调用的函数,在文章末尾贴出
  2. int main()
  3. {
  4. int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
  5. writew_lock(fd);
  6.  
  7. //child 1
  8. if (fork() == 0)
  9. {
  10. sleep(3);
  11.  
  12. cout<<"child 1 try to get write lock..."<<endl;
  13. writew_lock(fd);
  14. cout<<"child 1 get write lock..."<<endl;
  15.  
  16. unlock(fd);
  17. cout<<"child 1 release write lock..."<<endl;
  18.  
  19. exit(0);
  20. }
  21.  
  22. //child 2
  23. if (fork() == 0)
  24. {
  25. cout<<"child 2 try to get read lock..."<<endl;
  26. readw_lock(fd);
  27. cout<<"child 2 get read lock..."<<endl;
  28.  
  29. unlock(fd);
  30. cout<<"child 2 release read lock..."<<endl;
  31.  
  32. exit(0);
  33. }
  34.  
  35.  sleep(10);
  36. unlock(fd);
  37.  
  38. return 0;
  39. }

在Linux 2.6.18下执行结果:

  1. child 2 try to get read lock...
  2. child 1 try to get write lock...
  3. child 2 get read lock...
  4. child 2 release read lock...
  5. child 1 get write lock...
  6. child 1 release write lock...

将上面代码该成child2 sleep 3s,child1不sleep

  1. //调用的函数,在文章末尾贴出
  2. int main()
  3. {
  4. int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
  5. writew_lock(fd);
  6.  
  7. //child 1
  8. if (fork() == 0)
  9. {
  10. cout<<"child 1 try to get write lock..."<<endl;
  11. writew_lock(fd);
  12. cout<<"child 1 get write lock..."<<endl;
  13.  
  14. unlock(fd);
  15. cout<<"child 1 release write lock..."<<endl;
  16.  
  17. exit(0);
  18. }
  19.  
  20. //child 2
  21. if (fork() == 0)
  22. {
  23. sleep(3);
  24.  
  25. cout<<"child 2 try to get read lock..."<<endl;
  26. readw_lock(fd);
  27. cout<<"child 2 get read lock..."<<endl;
  28.  
  29. unlock(fd);
  30. cout<<"child 2 release read lock..."<<endl;
  31.  
  32. exit(0);
  33. }
  34.  
  35. sleep(10);
  36. unlock(fd);
  37.  
  38. return 0;
  39. }

在Linux 2.6.18下执行结果如下:

  1. child 1 try to get write lock...
  2. child 2 try to get read lock...
  3. child 1 get write lock...
  4. child 1 release write lock...
  5. child 2 get read lock...
  6. child 2 release read lock...

由上可知在Linux 2.6.18下,等待的写入锁进程和读出锁进程的优先级由FIFO的请求顺序进程响应。

6记录锁的使用封装

  1. void lock_init(flock *lock, short type, short whence, off_t start, off_t len)
  2. {
  3. if (lock == NULL)
  4. return;
  5.  
  6. lock->l_type = type;
  7. lock->l_whence = whence;
  8. lock->l_start = start;
  9. lock->l_len = len;
  10. }
  11.  
  12. int readw_lock(int fd)
  13. {
  14. if (fd < 0)
  15. {
  16. return -1;
  17. }
  18.  
  19. struct flock lock;
  20. lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0);
  21.  
  22. if (fcntl(fd, F_SETLKW, &lock) != 0)
  23. {
  24. return -1;
  25. }
  26.  
  27. return 0;
  28. }
  29.  
  30. int writew_lock(int fd)
  31. {
  32. if (fd < 0)
  33. {
  34. return -1;
  35. }
  36.  
  37. struct flock lock;
  38. lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);
  39.  
  40. if (fcntl(fd, F_SETLKW, &lock) != 0)
  41. {
  42. return -1;
  43. }
  44.  
  45. return 0;
  46. }
  47.  
  48. int unlock(int fd)
  49. {
  50. if (fd < 0)
  51. {
  52. return -1;
  53. }
  54.  
  55. struct flock lock;
  56. lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0);
  57.  
  58. if (fcntl(fd, F_SETLKW, &lock) != 0)
  59. {
  60. return -1;
  61. }
  62.  
  63. return 0;
  64. }
  65.  
  66. pid_t lock_test(int fd, short type, short whence, off_t start, off_t len)
  67. {
  68. flock lock;
  69. lock_init(&lock, type, whence, start, len);
  70.  
  71. if (fcntl(fd, F_GETLK, &lock) == -1)
  72. {
  73. return -1;
  74. }
  75.  
  76. if(lock.l_type == F_UNLCK)
  77. return 0;
  78. return lock.l_pid;
  79. }

Jun 28, 2013 PM16:06 @lab  困呀。。。

Linux进程同步之记录锁(fcntl)的更多相关文章

  1. linux 文件记录锁详解

    一: linux记录锁更恰当的称呼应该是范围锁,它是对文件某个范围的锁定. 关于记录锁的功能就是fcntl提供的第五个功能,具体使用如下: int fcntl(int fd, int cmd, str ...

  2. fcntl记录锁实例

    fcntl 函数是一个相当常用的对文件进行加锁操作的函数. 文件锁包括强制锁.建议锁.记录锁, 不过一般系统和内核都是用的强制锁. 以下为记录锁的例子:------------------------ ...

  3. linux c编程:记录锁

    记录锁相当于线程同步中读写锁的一种扩展类型,可以用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,通过fcntl函数来执行上锁操作.尽管读写锁也可以通过在共享内存区来进行进程的同步,但是fcntl记 ...

  4. Linux进程同步之POSIX信号量

    POSIX信号量是属于POSIX标准系统接口定义的实时扩展部分.在SUS(Single UNIX Specification)单一规范中,定义的XSI IPC中也同样定义了人们通常称为System V ...

  5. 高级I/O之记录锁

    若两个人同时编辑一个文件,其后果将如何呢?在很多UNIX系统中,该文件的最后状态取决于写该文件的最后一个进程.但是对于有些应用程序(例如数据库),进程有时需要确保它正在单独写一个文件.为了向进程提供这 ...

  6. linux进程同步机制_转

    转自:Linux进程同步机制 具体应用可参考:线程同步       IPC之信号量 为了能够有效的控制多个进程之间的沟通过程,保证沟通过程的有序和和谐,OS必须提供一 定的同步机制保证进程之间不会自说 ...

  7. inux c编程:记录锁

    记录锁的功能:当一个进程正在读或修改文件的某个部分是,它可以阻止其他进程修改同一文件区.对于这个功能阐述我认为有三点要解释的: 记录锁不仅仅可以用来同步不同进程对同一文件的操作,还可以通过对同一文件加 ...

  8. [11]APUE:(文件)记录锁

    [a] 概念 建议锁:在遵循相同记录锁规则的进程间生效,通常用于保证某个程序自身多个进程间的数据一致性 强制锁:意在保证所有进程间的数据一致性,但不一定有效:如不能应对先 unlink 后建立同名副本 ...

  9. 非阻塞io与记录锁

    非阻塞io 1.对比 阻塞io的例子:scanf从终端获取输入时,如果不输入程序就会一直停在那; 对一个已经有写锁的文件请求读时, 会一直空等直到前面的进程释放锁... 非阻塞的例子:读取文件内容, ...

随机推荐

  1. MySql截取DateTime字段的日期值

    用 DATE_FORMAT 来格式化日期字段 SELECT DATE_FORMAT(crt_time,'%Y-%m-%d') FROM ad_n_advertise_t

  2. Servlet的学习之Session(5)

    在上一篇中我们介绍了如果使用Session来做一个简单的用户登录案例,在本篇中我们继续使用Session技术来做一个防止表单重复提交的案例. 这是一个很重要的知识点,在很多框架中都有防止表单重复提交的 ...

  3. Spring Boot,Spring Data JPA多数据源支持

    1 配置文件 wisely.primary.datasource.driverClassName=oracle.jdbc.OracleDriver wisely.primary.datasource. ...

  4. java实现点卡生成

    点卡主要有2部分:卡号和密码.卡号一般由数字组成,密码就不多说了. java中随机数很强大,大家可以自己查.卡号生成使用java中随机数,密码使用uuid,密码可以自己再加点东西之类的.下面是完整代码 ...

  5. uva 140

    思路:暴力+剪枝 uva140 wa了好多次……数组开小了……!!! #include <iostream> #include <cstdio> #include <cm ...

  6. boost function对象

    本文根据boost的教程整理. 主要介绍boost function对象的用法. boost function boost function是什么 boost function是一组类和模板组合,用于 ...

  7. c++中编译器的作用

    编译器的部分工作是寻找程序代码中的错误.编译器不能查出程序的意义是否正确. 但它能够查出程序形式上的错误.以下是编译器能查出的最普遍的一些错误: (1)语法错误.程序猿犯了c++语言中的语法错误. ( ...

  8. MongoDB学习笔记(四) 用MongoDB的文档结构描述数据关系

    MongoDB的集合(collection)可以看做关系型数据库的表,文档对象(document)可以看做关系型数据库的一条记录.但两者并不完全对等.表的结构是固定的,MongoDB集合并没有这个约束 ...

  9. 如何去掉List中的重复内容

    1.通过循环进行删除 public static void removeDuplicate(List list) { ; i < list.size() - ; i ++ ) { ; j > ...

  10. NDK-gdb的错误ERROR(不同于上一篇): Could not extract package's data directory...的解决方法

    这个问题比较龟毛. 我的系统在4.0.4上一直调试好好的,到了2.2的系统居然fail.能检查的地方全部检查过了,居然不行. 最后仔细差了一遍,居然是由于/data目录的属性是777导致.ndk-gd ...