struct var_data
{
int len;
char data[0];
}
struct var_data s;
for (i = 0; i < s.len; i++)
printf("%02x \n", s.data[i]);
==================================================================================
switch (ch)
{
case '0'... '9': c -= '0';
break;
case 'a'... 'f': c -= 'a' - 10; //A 65 a 97 '' 32
break;
case 'A'... 'F': c -= 'A' - 10;
break;
}
==================================================================================
#define min_t(type,x,y) \
({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })
int ia, ib, mini;
float fa, fb, minf;
mini = min_t(int, ia, ib);
minf = min_t(float, fa, fb);
下面一个更好的
==================================================================================
typeof(x)语句可以获得x 的类型,因此,我们可以借助typeof 重新定义min 这个宏:
#define min(x,y) ({ \
const typeof(x) _x = (x); \
const typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; }) // 为啥加大括号? 因为里面是一段代码。
首先,我们此处想要实现的目的是,在计算两个数的最小值之前,希望去判断一下两个值的类型是否一致,而由于C语言本身不支持我们去做类似于这样的操作typeof(_x)==typeof(_y),所以在此,通过故意判断他们2个的地址指针是否相等,而显然&_x,即x的地址,是不可能等于&_y的,但是这句话(void) (&_x == &_y);使得,如果_x和_y的类型不一样,其指针类型也会不一样,2个不一样的指针类型进行比较操作,则会引起编译器产生一个编译警告,提示你这两个值的类型不同。
(void) (&_x == &_y); 用于判断输入的两个值的类型是否是一致的。如果不一致,那么编译器就会做出如下警告:warning: comparison of distinct pointer types lacks a cast
(void) (&_x == &_y); 中的void,表示将表达式(&_x == &_y); 所得到的结果(此处肯定是逻辑上的假,值为0)忽略掉。如果不加void,则会提示你这行代码是无意义的,没人用到。
因为,如果如此定义,那么对于一些特殊的值传入此宏之后,就会产生一些副作用,产生的结果,就不是我们想要的了,比如:
min(++a,++b) ==> ((++a)<(++b))?(++a) : (++b)
就使得,a++和b++分别执行了2次,而且min的结果,也不对了。而用上面那个复杂的定义,多加了局部变量_x和_y,就可以避免此类问题了
==================================================================================
int printf( const char *format [, argument]... );
#define pr_debug(fmt,arg...) \
printk(fmt,##arg)
而在GNU C 中,宏也可以接受可变数目的参数,例如:
#define pr_debug(fmt,arg...) \
printk(fmt,##arg)
这里arg 表示其余的参数可以是零个或多个,这些参数以及参数之间的逗号构成 arg 的值,在宏扩展时替换 arg,例如下列代码:
pr_debug("%s:%d",filename,line)
会被扩展为:
printk("%s:%d", filename, line)
使用“##”的原因是处理arg 不代表任何参数的情况,这时候,前面的逗号就变得多余了。使用“##”之后,GNU C 预处理器会丢弃前面的逗号,这样,代码:
pr_debug("success!\n")
会被正确地扩展为:
printk("success!\n") // 没有逗号
而不是:
printk("success!\n",) //多了一个逗号
#define A1(name, type) type name_##type##_type 或
#define A2(name, type) type name##_##type##_type
A1(a1, int); /* 等价于: int name_int_type; */
A2(a1, int); /* 等价于: int a1_int_type; */
至于单独一个#,则表示 对这个变量替换后,再加双引号引起来。比如
#define __stringify_1(x) #x
那么
__stringify_1(linux) <==> ”linux”
==================================================================================
static char *book_name = "深入浅出Linux 设备驱动";
static int num = 4000;
module_param(num, int, S_IRUGO);
module_param(book_name, charp, S_IRUGO);
参数类型可以是 byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool 或invbool(布尔的反),
在模块被编译时会将module_param 中声明的类型与变量定义的类型进行比较,判断是否一致。
insmod hello.ko num=20
则可以 cat /sys/module/hello/parameters/num ,看到为 20
1.定义模块参数的方法:
module_param(name, type, perm);
其中,name:表示参数的名字;
type:表示参数的类型;
perm:表示参数的访问权限;
2. 数组类型模块参数的定义:
用逗号间隔的列表提供的值;
声明一个数组参数:
module_param_array(name, type, num, perm);
其中,name:表示数组的名字;
type:表示参数的类型;
num :表示数组中元素数量;
perm:表示参数的访问权限;
3.type支持的基本类型有:
bool :布尔类型
invbool:颠倒了值的bool类型;
charp :字符指针类型,内存为用户提供的字符串分配;
int :整型
long :长整型
short :短整型
uint :无符号整型
ulong :无符号长整型
ushort :无符号短整型
static char *book_name = "dissecting Linux Device Driver";
static int num = 4000;
static int book_init(void)
{
printk(KERN_INFO " book name:%s\n",book_name);
printk(KERN_INFO " book num:%d\n",num);
return 0;
}
static void book_exit(void)
{
printk(KERN_ALERT " Book module exit\n ");
}
module_init(book_init);
module_exit(book_exit);
module_param(num, int, S_IRUGO);
module_param(book_name, charp, S_IRUGO);
==================================================================================
Linux 2.6 的“/proc/kallsyms”文件对应着内核符号表,它记录了符号以及符号所在的内存地址。
模块可以使用如下宏导出符号到内核符号表:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名); 。EXPORT_SYMBOL_GPL()只适用于包含 GPL 许可权的模块
cat /proc/kallsyms | grep integar
==================================================================================
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
==================================================================================
对于USB、PCI 等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,如代码清单4.6 所示。
/* 对应此驱动的设备表 */
static struct usb_device_id skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID,
USB_SKEL_PRODUCT_ID) },
{ } /* 表结束 */
};
MODULE_DEVICE_TABLE (usb, skel_table);
此时,并不需要读者理解MODULE_DEVICE_TABLE 的作用,
==================================================================================
make –C /usr/src/linux-2.6.15.5 M=$(pwd) modules
==================================================================================
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
O_RDONLY 以只读的方式打开文件
O_WRONLY 以只写的方式打开文件
O_RDWR 以读写的方式打开文件
O_APPEND 以追加的方式打开文件
O_CREAT 创建一个文件
O_EXEC 如果使用了O_CREAT 而且文件已经存在,就会发生一个错误
O_NOBLOCK 以非阻塞的方式打开一个文件
O_TRUNC 如果文件已经存在,则删除文件的内容
open("test", O_CREAT, 10705);
int close(int fd);
==================================================================================
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);
其中参数buf 为指向缓冲区的指针,length 为缓冲区的大小(以字节为单位)。
函数read()实现从文件描述符fd 所指定的文件中读取length 个字节到buf 所指向的缓冲区中,返回值为实际读取的字节数。
函数write 实现将把length 个字节从buf 指向的缓冲区中写到文件描述符fd 所指向的文件中,返回值为实际写入的字节数。
==================================================================================
int lseek(int fd, offset_t offset, int whence);
SEEK_SET:相对文件开头。
SEEK_CUR:相对文件读写指针的当前位置。
SEEK_END:相对文件末尾。
由于lseek 函数的返回值为文件指针相对于文件头的位置, 因此下列调用的返回值就是文件的长度:
==================================================================================
FILE *fopen(const char *path, const char *mode);
r、rb 以只读方式打开
w、wb 以只写方式打开。如果文件不存在,则创建该文件,否则文件被截断
a、ab 以追加方式打开。如果文件不存在,则创建该文件
r+、r+b、rb+ 以读写方式打开
w+、w+b、wh+ 以读写方式打开。如果文件不存在,则创建新文件,否则文件被截断
a+、a+b、ab+ 以读和追加方式打开。如果文件不存在,则创建新文件
==================================================================================
int fgetc(FILE *stream);
int fputc(int c, FILE *stream);
char *fgets(char *s, int n, FILE *stream);
int fputs(const char *s, FILE *stream);
int fprintf(FILE *stream, const char *format, ...);
int fscanf (FILE *stream, const char *format, ...);
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite (const void *ptr, size_t size, size_t n, FILE *stream);
另外,C 库函数还提供了读写过程中的定位能力,这些函数包括:
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
int fseek(FILE *stream, long offset, int whence);
int fclose (FILE *stream);
==================================================================================
linux存在一个虚拟文件系统/sysfs,内核启动挂接文件系统后会将sysfs文件系统挂接到/sys下。
内核启动时会初始化并注册一些总线、设备,这些总线、设备等会在sys下创建目录,来存储,如/sys/bus/platform 下存储了平台设备信息。
如何组织管理设备,并创建目录呢,通过设备驱动模型的几个重要结构体:Kobject、kobj_type、kset来组织和管理目录及文件结构。
==================================================================================
内存地址可以直接由C 语言指针操作,例如在186 处理器中执行如下代码:
unsigned char *p = (unsigned char *)0xF000FF00;
*p=11;
以上程序的意义为在绝对地址0xF0000+0xFF00(186 使用16 位段地址和16 位偏移地址)写入11。
再如,186 处理器启动后会在绝对地址0xFFFF0(对应C 语言指针是0xF000FFF0,0xF000 为段地址,0xFFF0 为段内偏移)执行,请看下面的代码:
typedef void (*lpFunction) ( ); /* 定义一个无参数、无返回类型的函数指针类型*/
lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定义一个函数指针,指向*/
/* CPU 启动后所执行第一条指令的位置 */
lpReset(); /* 调用函数 */
在以上程序中,没有定义任何一个函数实体,但是程序中却执行了这样的函数调用:lpReset(),
它实际上起到了“软重启”的作用,跳转到CPU 启动后第一条要执行的指令的位置。
因此,可以通过函数指针调用一个没有函数体的“函数”,本质上只是换一个地址开始执行。
==================================================================================
但对于C语言的程序员,你应当将一个数据看作两个内容:存储空间和数值,
作用域包含两个维度:范围和生命周期。
则有跨越c文件的全局数据、文件内的局部数据、函数内局部数据,被{}所包含的代码片的局部数据。
简单说,正常情况下,任何函数的参数,总需要通过复制的方式,将数据传递给被调用的函数使用。
unsigned int i = 100;
while (i >= 0)
i--
这将是一个死循环
对于这类计数器,更合理的做法则是:
·尽量不要写>=的方式,必要时改用do{}while(time);
·使用无符号整型传递参数。
对于移位操作,左移等于乘以2,有无符号没有差异;
而右移则不一样,有符号的整型,总会在每次右移一位时保持最高位的值不变,
而无符号的整型操作,则在每次右移一位时总是填写0。
而在以一款ARM作为目标系统下编译时,编译器默认char为无符号类型
我更希望无论是否从事嵌入式开发,作为初级程序员都能养成这个习惯,将C语言的基础类型全部重新定义,无论它是否需要跨平台移植 。
typedef signed char _i8;
typedef unsigned char _u8;
typedef signed int _i32;
typedef unsigned int _u32;
typedef char _c;
typedef char * _s;
==================================================================================
typedef struct{
_i32 i;
_i8 c;
_u16 us;
}_XXX;
这实际是将struct{}通过typedef重定义为一个_XXX的类型。
访问一个结构体类型的存储空间中的内部成员,可通过该数据名加“.”加成员名来实现(简称为“直接方式”)
==================================================================================
_XXX X1[1];// 已经明确里数据存储空间,这已经是一个数据常量,x1指向该数据的首地址
_XXX *X2; //不会产生数据存储空间,并没有初始化,所以内部存储数据可以变
它们的差异在于如下两点。
1)X1[1]存在明确的_XXX类型的数据存储空间,而X2的定义本身不会产生_XXX类型的数据存储空间。
2)X1是一个指针常量数值(简称指针常量),它始终指向上述_XXX类型的连续数据存储空间的首单元位置;
而X2是个数据,它具有一个存储指针类型的存储空间(该空间因为没有const的定义,所以内部存储的数值还可以改变)。
//这个很重要,需要好好理解
因为X1是个指针常量,而不是数据,并不存在一个存储空间存放X1的数值,因此&X1仍然是X1。
X2是数据,因此可以通过&X2获取到X2的数据存储空间的自身地址
==================================================================================
char X423[4][2][3];
char (*pX23)[2][3];
X423的含义是:存在一个三维连续存储空间,最低维包含3个char的单元;
第二维包含2个单元,每个单元是最低维的整体空间;
最高维包含4个第二维整体空间类型的单元;该空间的地址由一个指针常量X423所表示。
pX23的含义为:一个指针,它指向一个连续空间中首单元的地址,该单元的类型是一个二维连续空间,
其中最低维是包含3个char的单元,
第二维包含2个单元,每个单元是最低维的整体空间。
X423[1]的含义表示X423所指向的三维连续存储空间第一维中第2个单元整体,数值上为该空间的地址。
编译器通过定义中的内容可自动算出它和X423[0]的差值。
==================================================================================
gcc hello.c -I/root/workplace/gcc/ -o hello // -I include //不需要指定明确路径
gcc hello.c -L/root/workplace/gcc/lib-lsunq-o -o hello // -L 需要指定文件具体路径
gcc -o dynamic -L/root/xxxx/lib/dynamic.o -lmdynamic //-lm 指定静态文件
==================================================================================
0x0000fff0L L,l 表示长整形, U,u表示 无符号整型
%d 10进制
%x 16进制
%o 8进制
int a;
float c;
scanf(“%3d%*4d%f”, &a, &c);// 这些抑制符只能再 scanf 上用
a,c 公用一个数字 1234567890
a=123
c=890.000000
printf %d %x %o %u %c %s %e %f
数组参数 等效的指针参数
一维数组 char a[30] 指针 char*
指针数组 char *a[30] 指针的指针 char **a
二维数组 char a[10][30] 数组的指针 char(*a)[30]
(*p)() int (*)() p
(*p[])() int (*)() p[]
Char * c_keyword[]={
“while”, “case”, “static” , “do”, NULL }
‘\0’ 与 0 与 NULL 是一样的。
\是转义 ‘\0’的意思就是 0
系统定义 #define NULL 0
其中有个知识点需要记住,
Char a[]={“111”,”222”,”33”}; //这是个数组,是在栈空间开辟空间,111.222.333 直接在栈的 a[0]a[1]a[2]中
Char *a[]={“111”,”222”,”33”}; //这是个指针数组,在栈开辟空间,但是 111.222.333 是在全局区的
Char a[3][4]={“111”,”222”,”33”}; //这是个二维数组,是在栈开辟空间,111.222.333 直接在栈的 a[0]a[1]a[2]中
*to = *from ; //*号就像一把钥匙,通过 from 内容中的地址(&t1 的地址),打开一扇门,修改门后的内存空间。
放在右边相当于读取这个内存空间,读内存,拿值。
放在左边相当于写入这个内存空间,写内存,写值。
可用二级指针指向以及指针 //结构体左函数参数的进阶
思路:通过二级指针做形参改变实参一级指针的值。
Int creatTeacher (Teacher **pArray,int num)
{
Teacher *tmp=NULL; //在被调用函数里分配空间是为了做输出
Tmp=(teacher *)malloc(sizeof(teaccher)*num);
If(tmp==NULL) ......
*pArray=tmp;
//实参是一级指针指向某个值,一级指针存的是某个值的地址
//现在需要改变一级指针里面的地址,所以需要用到二级指针
//在函数里面开辟一个一级指针的空间(要改变的是一级指针),存入值,strcpy,memcpy都可以
//将刚开辟的空间的地址(是内容的地址不是&他的地址)赋值给需要改变的一级地址(需要改变的是一级地址,我们用二级指针指向他来改变)
//
Return tmp;
}
Main(){
Teacher *pArray=NULL; //一级指针
Ret=creatTeacher(&pArray,num)
}
详细理解: 1、首先要改变一级指针的值必须要定义二级指针。
2、被调用函数里面要做输出,必须开辟内存空间
3、步骤: 1、定义以二级指针作为形参的函数
2、定义一个一级指针开辟 malloc 空间
3、将 malloc 的空间赋给*p
为什么是*p 呢?
1、二级指针的地址(&p)是本身的地址,三级指针才用
2、二级指针的 p 是 tmp 的地址(&tmp),
3、二级指针的*p 是 tmp 的内容(tmp)
3、更详细的解释:把 tmp 的内容付给 p 指向的地址(&pArray)
--------------------------------
*pArray 是一级指针的内容(是一级指针指向内容的地址)
**pArray 是指向一级指针指向的内容
pArray 是一级指针的地址
注意以下两点:
(1)如果 p 的初值为 stu,即指向第一个元素,则 p + 1 后指向下一个元素的起始地址。例如:
(++p) -> num 先使 p 自加 1 ,然后得到它指向的元素中的 num 成员的值(即 10102)。
(p++) ->num 先得到 p->num 的值(即 10101),然后使 p 自加 1 ,指向 stu[1]。
注意以上二者的不同。
(2)程序已定义了指针 p 为指向 struct student 类型数据的变量,它只能指向一个 struct student 型的数据
(p 的值是 stu 数组的一个元素的起始地址),而不能指向 stu 数组元素中的某一成员,(即 p 的地址不能是
成员地址)。例如,下面是不对的:
p = &stu[1].name
编译时将出错。千万不要认为反正 p 是存放地址的,可以将任何地址赋给它。如果地址类型不相同,可以用强制
类型转换。例如:
p = (struct student *)&stu[1].name;??? 对吗??
此时,在 p 中存放 stu[1] 元素的 name 成员的起始地址
结构体的深拷贝和浅拷贝
//编译器的=号操作,只会把指针变量的值,从 from 拷贝到 to,但不会把指针变量所指的内存空间给拷贝过去。
所以需要深拷贝 还是需要在开辟空间来存储
如果你想执行深拷贝。
先浅拷贝在深拷贝
Void copyTeacher(Teacher *to, Teacher *from)
{
*to=*from; //先把指针里面的地址值拷贝过去
//memcpy(*to,*from,sizeof(Teacher));
To->pname2=(char *)malloc(100); //又开辟了一个空间
Strcpy(to->pname2,from->pname2);
}
指针不能加指针,不能相乘。能加 1 ,减 1, + sizeof(类型) -sizeof(类型)
&(p->age);
//Int offsize=(int)&(p->age); //64
Int offsize=(int)&(p->age)-(int)p //这个是相对于 p 的偏移量
Int offsize=(int)&(((advTeacher*)0)->age) // 也是 64 这个是相对于地址 0 的偏移量
相当于
advTeacher *p=NULL;
Int offsize=(int)&(p->age);
压缩
tar -cvf jpg.tar *.jpg //将目录里所有jpg文件打包成tar.jpg
tar -czf jpg.tar.gz *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用gzip压缩,生成一个gzip压缩过的包,命名为jpg.tar.gz
tar -cjf jpg.tar.bz2 *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用bzip2压缩,生成一个bzip2压缩过的包,命名为jpg.tar.bz2
tar -cZf jpg.tar.Z *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用compress压缩,生成一个umcompress压缩过的包,命名为jpg.tar.Z
rar a jpg.rar *.jpg //rar格式的压缩,需要先下载rar for linux
zip jpg.zip *.jpg //zip格式的压缩,需要先下载zip for linux
解压
tar -xvf file.tar //解压 tar包
tar -xzvf file.tar.gz //解压tar.gz
tar -xjvf file.tar.bz2 //解压 tar.bz2
tar -xZvf file.tar.Z //解压tar.Z
unrar e file.rar //解压rar
unzip file.zip //解压zip
总结
1、*.tar 用 tar -xvf 解压
2、*.gz 用 gzip -d或者gunzip 解压
3、*.tar.gz和*.tgz 用 tar -xzf 解压
4、*.bz2 用 bzip2 -d或者用bunzip2 解压
5、*.tar.bz2用tar -xjf 解压
6、*.Z 用 uncompress 解压
7、*.tar.Z 用tar -xZf 解压
8、*.rar 用 unrar e解压
9、*.zip 用 unzip 解压
【1】gcc 四个步骤:
1,预处理(Pre-Processing)
-E to .i
gcc -E test.c -o test.i
1)头文件包含
2)宏替换
2,编译(Compiling)
-S to .s
gcc -S test.i -o test.s
3,汇编(Assembling)
-c to .o
gcc -c test.s -o test.o
-c 通常用于编译不包含主程序的子程序文件。
4,链接(Linking)
gcc test.o -o test
// 可以在编译时,定义宏
gcc def.c -o def -D_DEBUG_
0xa6f=ax16^2+6x16^1+fx16^0=2560+96+15
a=1010 6=0110 f=1111
101001101111=2671
a='F' 将70送给a 相当于a=70
b='A'+2 b存放的是'C'字符 相当于b=65+2
u=''+'B'u存放的是'b'字符 相当于u=32+66
v='b'-32存放的是'B'字符 相当于v=97-32
(int)'9'=57
(int)'A'=65
(int)'0'=48
(int)'a'=97
赋值运算符<逻辑与运算符<关系运算符<算术运算符
= & | > < + - * / %
格式字符
i d 十进制整数
x X 十六进制无符号整数 # 表示加上0x
o 八进制无符号整数
u 十六进制无符号整数
c 单一字符
s 字符串
e E 指数形式浮点小数
f 小数形式浮点小数
g e和f中较短的一种
%% %本身
附加格式说明符
m 输出数据宽 <m, 左补空格,否则按实际输出
.n 对实数,指定小数点后位数 四舍五入
对字符串,指定输出位数
- 输出数据在域内左对齐(缺省是右对齐)
+ 指定在有符号数的正数前显示+号
0 输出竖数值时指定左面补使用的空位置自动填0
# 在八进制和16进制前显示 0 和0x
l 在d,o,x,u, 前输出精度为long
l 在e,f,g前,输出精度为double
* 抑制符号 指定输入项读入后补赋值给变量
h 用于d,o,x,指定输入为short型整数
int a[2][3] int (*p)[3] p是行指针
重点 gg=G = 自动缩进
ctrl+] 查找内核
shift+k 相当于man
man -k xxx 列出相关的
vim -t xxx
vim -O xx.c xxx.c
ctrl+d 关闭
:s/str1/str2/ 替换
/g
:1,$/str1/str2/g 第一行到最后一行
:.,$/str1/str2/g 当前到最后一行
:%$/str1/str2/g 行头到行尾
【13】uniq //去掉文件中相邻重复的行
uniq test.txt // 实际并没有改变文件的内容
skrip 瘦身
可以 alias xxx=tput reset
set alias xxx:=tput reset 放在.bashrc 中
之后 source .bashrc 或者 . .bashrc
进程的5种状态:
D uninterruptible sleep (usually IO)
R running or runnable (on run queue)
S interruptible sleep (waiting for an event to complete)
T stopped, either by a job control signal or because it is being traced.
W paging (not valid since the 2.6.xx kernel)
X dead (should never be seen)
Z defunct ("zombie") process, terminated but not reaped by its parent.
For BSD formats and when the stat keyword is used, additional characters may be displayed:
< high-priority (not nice to other users)
N low-priority (nice to other users)
L has pages locked into memory (for real-time and custom IO)
s is a session leader
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)
+ is in the foreground process group.
1)命令行配置ip地址
配置ip地址:interfaces
sudo vi /etc/network/interfaces
iface eth0 inet static
address 192.168.2.196
netmask 255.255.255.0
gateway 192.168.2.1
配置dns域名解析服务器: resolv.conf
sudo vi /etc/resolv.conf
nameserver 192.168.2.1
重启使配置文件生效:
sudo /etc/init.d/networking restart
注意点:
/etc/resolv.conf 每次开机重启后,都会被清零。
修改启动脚本: rc.local本地配置脚本 .bashrc差不多
sudo vi /etc/rc.local
echo "nameserver 192.168.2.1" >> /etc/resolv.conf
【3】gdb 调试
gcc -g hello.c -o hello // 生成调试符号表
查看变量值:
查看文件
(gdb) l list
设置断点
(gdb) b 6 break
查看断点情况
(gdb) info b
运行代码
(gdb) r run
(gdb) p n print
单步运行
(gdb) n next
(gdb) s step
恢复程序运行
(gdb) c continue
d 取消断点
delete
clear
打印所有局部变量的值
backtrace full
帮助
(gdb) help [command]
/// 调试段错误,可以使用 gdb ,直接锁定错误行号
///
指针与二维数组:
int a[3][4] = {};
int *p = a; // 错误,因为二者的地址的含义不一样
// 针对二维数组的数组名,专门引入数组指针概念。
int (*p)[4]; // p 是一个数组指针,表示指向一个包含4个整形元素的一维数组的指针
p = a;
*(*(p+i)+j)
*(*(a+i) + j) <==> *(a[i] + j) <==> a[i][j] <==> *(*(p+i)+j) <==> *(p[i] + j) <==> p[i][j];
【8】const 关键字
将变量常量化, 将const修饰的类型符去掉,const 修饰谁,那么谁就只读
int * const p; // p
int const *p; // *p
const int *p; // *p
const int * const p; // p, *p
【12】函数指针
本质是一个指针,指向一个函数。
int add(int a, int b);
int (*p)(int a, int b); // int (*)(int a, int b) p; c语言语法不支持
// 这样理解非常好
【13】函数指针数组
本质是一个数组,数组中每一个元素,都是一个函数指针。
int (*p[4])(int a, int b); // int (*)(int a, int b) p[4]; 可以这么理解
void *(*p[4])(void *)
【16】结构体
struct 结构体名
{
数据类型 成员名1;
数据类型 成员名2;
:
数据类型 成员名n;
};
struct 结构体名 变量名; // 开辟了空间
【19】共用体
成员共享一段地址空间,空间大小由最大的成员来决定。
大端存储:
高地址存储低字节,低地址存储高字节
小端存储:
高地址存储高字节,低地址存储低字节
// 0x12345678 12 高字节 , 78 低字节
【22】malloc 在堆区分配空间
malloc/free
void * malloc(size_t num) //void* 会自动转换
void free(void *p)
野指针:
不是NULL指针,是指向“垃圾”内存的指针。“野指针”是很危险的。
“野指针”的成因主要有两种:
指针变量没有被初始化。
指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。
指针操作超越了变量的作用范围。这种情况让人防不胜防
#include <stdio.h>
//默认是4字节对齐
struct {
int a; //4
char b; //4
}s1; //8
struct {
int a; //4
char b; //1 //下合并 4
char c; //1
}s2; //8
struct {
char b; //1
int a; //4
char c; //1
}s3; //12
struct {
short a;
short b;
short c;
}s4; //6
struct {
int a; //4
double b; //8
short c; //2 //按四节对齐,最小是4即可
}s5; //16
struct {
int a; //4
char b; //1
short d; //2
short c; //2
}s6; //12
1、设置权限
setfacl -m u:user1:rwx /test
2、查看权限
getfacl /test
3、删除user1在/test上的权限
setfacl -x u:user1 /test
4、删除/test上所有的acl权限
setfacl -b /test
5、设置acl的默认权限
setfacl -m d:u:user1:rwx test
创建和删除的文件权限
只需要对文件设置acl权限即可
没有x表示不能cd进去,没有w表示不能创建文件
#include<stdio.h>
typedef union um
{
int a;
char ch;
}myum;
int get_num(myum *p)
{
p->a=0x87654321;
return 0;
}
int main(int argc, const char *argv[])
{
int i=0;
myum umcheck;
char *p;
umcheck.a=0x12345678;
get_num(&umcheck);
printf("%#x \n",umcheck.a);
p=(char*)(&umcheck.ch);
printf("%p,%x\n",p,*p);
p=(char*)(&umcheck.a);
for(i=0;i<4;i++)
{
printf("union中的a的地址%p,%x\n",p+i,*(p+i));
}
printf("union中的a的地址%p,%x\n",p+3,*(p+3));
return 0;
}
linux命令
su (-m -p) 执行su时补改变环境变量
uptime 显示系统已经运行了多长时间
ps ajxwwww 显示较多的信息
kill -p 打印出pid 不发送信号
kill -l 显示信号名称
/var/log/messages 找到linux系统辨认出来的设备号
mount -t(类型 type) vfat
ls -lhi
mkdir -m 777 ./why
mkdir -p ./hello/my
cat (-n -b) 行数/对空白行不编号
cp -r 递归
-i 交互
-f 不显示提示
grep -I 不区分大小写 -n显示行号 -v -w -h 不显示文件名 -s 不提示错误信息
-r 明确要求搜索子目录
-d 忽略子目录
ln -s 软连接 没有s是硬链接
中断:
轮询polling 让内核定期对设备的状态进行查询,然后做出相应的处理
中断interrupt 让硬件在需要的时候向内核发出信号。变内核主动为硬件主动。
中断控制器:包括屏蔽和状态两个寄存器。
中断的功能仅仅是产生了一个报警事件,当事件发生的时候中断处理程序只知道事情发生了。
当发生了什么事情,还要亲自到设备里去看才知道。
设备是通过中断线向中断控制器发送高电平,告诉操作系统它产生了一个中断,而操作系统会从中断控制器的状态位知道是哪条中断线上产生了中断。
并不是每个设备都可以向中断线上发送信号。,只有对某一条确定的中断线拥有了控制权,才可以向这条中断线发送信号。
中断线是非常宝贵的资源。要使用中断线,就得进行中断线的申请,即IRQ,通常把申请一条中断线称为申请一个IRQ,或者申请一个中断号
目前在大多数嵌入式平台上,每个设备的中断号都已经被固定分配好了。不在改变。
/arch/arm/mach-s5pc100/include/mach/irqs.h
linux系统的中断通常就是 中断处理程序 直接处理由硬件发过来的中断信号
linux内核与中断相关的部分包括硬件中断,下半部和内核线程几种。
硬件中断任务:
是由哪些处理器以外的外设产生的中断。
这些中断被处理器接收后交给内核中中断处理程序处理。
1、硬件中断是异步产生的。中断发生后,会立即得到处理。也就是说中断操作可以抢占内核中的正在运行的代码
2、中断操作是发生在中断上下文的(和任何进程无关的上下文环境)。
中断上下文不可以使用进程相关的资源,也不可以进行调度和睡眠,因为调度会引起睡眠,当睡眠必须是针对进程而言。
异步发生的中断处理程序根本不知道当前进程的任何信息,也不关心当前哪个进程在运行,它完全是个过客下半部任务硬件中断任务(处理程序)是一个快速,异步,简单的对硬件做出响应,并在最短时间内完成必要操作的中断处理程序,
硬件中断处理可以抢占内核任务,并且执行时还会屏蔽同级中断或其他中断,因此中断必须要快,不能阻塞。
所以,linux将任务分割成2个部分:
1、中断处理程序,力求短平快的处理与硬件相关的操作(如网卡读数据到系统缓存)
2、而把对时间要求相对宽松的任务,(如解析工作),放在另一个部分来执行,这个部分就是底半部,或者下半部
手段:tasklet 软中断softirq workqueue
软中断:
软中断机制用于系统中对时间要求最严格以及最重要的中断下半部。
核心元素: 软中断状态寄存器 soft interrupt state(irq_stat)
软中断向量表 softirq_vec
软中断守护 daemon
当某一软中断时间发生后,首先需要设置对应的中断标志位,触发中断事务,然后唤醒守护线程去检测中断状态寄存器。
如果通过查询发现某一个软中断事务发生,那么通过软中断向量表调用软中断程序aciton(),
与硬件中断请求不同的在于从中断标记到中断服务程序的映射过程。
1、在CPU硬件中断发生之后,CPU需要将硬件中断请求通过向量表映射成具体的服务程序,这个过程是硬件自动完成的。
2、但是软中断不是,需要守护线程去实现,这一过程。也就是软中断模拟中断。所以叫软中断。
linux最多可以处理32个软中断,目前系统使用了6个软中断。
他们分别定时器处理,SCSI处理,网络收发处理以及tasklet机制。
分别是定时器处理,SCSI处理,网络收发处理以及tasklet机制。
网上查下 软中断机制。 arm中写了 swi
strcpy和memcpy的区别
strcpy和memcpy都是标准C库函数,它们有下面的特点。
strcpy提供了字符串的复制。即strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符。
已知strcpy函数的原型是:char* strcpy(char* dest, const char* src);
memcpy提供了一般内存的复制。即memcpy对于需要复制的内容没有限制,因此用途更广。
void *memcpy( void *dest, const void *src, size_t count );
char * strcpy(char * dest, const char * src) // 实现src到dest的复制
{
if ((src == NULL) || (dest == NULL)) //判断参数src和dest的有效性
{
return NULL;
}
char *strdest = dest; //保存目标字符串的首地址
while ((*strDest++ = *strSrc++)!='\0'); //把src字符串的内容复制到dest下
return strdest;
}
void *memcpy(void *memTo, const void *memFrom, size_t size)
{
if((memTo == NULL) || (memFrom == NULL)) //memTo和memFrom必须有效
return NULL;
char *tempFrom = (char *)memFrom; //保存memFrom首地址
char *tempTo = (char *)memTo; //保存memTo首地址
while(size -- > 0) //循环size次,复制memFrom的值到memTo中
*tempTo++ = *tempFrom++ ;
return memTo;
}
strcpy和memcpy主要有以下3方面的区别。
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
C\C++中strcat()函数
把src所指字符串添加到dest结尾处(覆盖dest结尾处的'\0')。
char d[20]="GoldenGlobal"; char *s="View"; strcat(d,s);
结果放在d中
printf("%s",d);
输出 d 为 GoldenGlobalView (中间无空格)
d和s所指内存区域不可以重叠且d必须有足够的空间来容纳s的字符串。
返回指向d的指针。
/将源字符串加const,表明其为输入参数
char* strcat(char* strDest , const char* strSrc)
{
//后文return address,故不能放在assert断言之后声明address
char* address=strDest;
assert( (strDest!=NULL)&&(strSrc!=NULL) );//对源地址和目的地址加非0断言
while(*strDest)//是while(*strDest!=’\0’)的简化形式
{
//若使用while(*strDest++),则会出错,因为循环结束后strDest还会执行一次++,
//那么strDest将指向'\0'的下一个位置。/所以要在循环体内++;因为要使*strDest最后指
//向该字符串的结束标志’\0’。
strDest++;
}
while(*strDest++=*strSrc++)
{
NULL;//该循环条件内可以用++,
}//此处可以加语句*strDest=’\0’;无必要
return address;//为了实现链式操作,将目的地址返回
}
char *mystrcat(char *dst,const char *src) //用自己的方式实现strcat函数功能
{
char *p=dst; //下面的操作会改变目的指针指向,先定义一个指针记录dst
while(*dst!='\0')dst++;
while(*src!='\0')*dst++=*src++;
*dst='\0';
return p; //dst现在指向拼接后的最后一位字符,在这里返回dst,会出现错误
}