热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

字符设备驱动leds

内核版本:4.12.9编译器:arm-linux-gcc-4.4.3本驱动基于jz2440v2开发板,实现3个led设备的驱动程序。代码如下:1#include

内核版本:4.12.9

编译器:arm-linux-gcc-4.4.3

本驱动基于jz2440 v2开发板,实现3个led设备的驱动程序。

代码如下:


技术分享图片技术分享图片

1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11
12 #define DEVICE_NAME "leds"
13 #define LED_MAJOR 231
14
15 #define LED_COUNT (4)
16 int major;
17 int minor;
18 static struct cdev led_cdev;
19
20 static struct class *leds_class;
21 static struct device *leds_class_devs[4];
22
23
24 /* bit0<=>D10, 0:亮, 1:灭
25 * bit1<=>D11, 0:亮, 1:灭
26 * bit2<=>D12, 0:亮, 1:灭
27 */
28 static char leds_status = 0x0;
29 static DEFINE_SEMAPHORE(leds_lock);//定义赋值
30
31 volatile unsigned long *gpfcon = NULL;
32 volatile unsigned long *gpfdat = NULL;
33
34
35 #define S3C2410_GPF4 4
36 #define S3C2410_GPF5 5
37 #define S3C2410_GPF6 6
38
39 #define S3C2410_GPF4_OUTP 1
40 #define S3C2410_GPF5_OUTP 1
41 #define S3C2410_GPF6_OUTP 1
42
43
44 static void s3c2410_gpio_cfgpin(int pin, int type)
45 {
46 *gpfcon &= ~(0x3<<(pin*2));
47 *gpfcon |= (1<<(pin*2));
48
49 }
50
51 static void s3c2410_gpio_setpin(int pin, int data)
52 {
53 if(data == 0)
54 *gpfdat &= ~(1<<pin);
55 else
56 *gpfdat |= (1<<pin);
57 }
58
59 /* 应用程序对设备文件/dev/leds执行open(...)时,
60 * 就会调用s3c24xx_leds_open函数
61 */
62 static int s3c24xx_leds_open(struct inode* inode, struct file* file)
63 {
64 int minor = MINOR(inode->i_rdev);
65
66 switch(minor)
67 {
68 case 0:/*/dev/leds*/
69 {
70 /* 配置GPF4,5,6为输出*/
71 *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
72 *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x01<<(6*2)));
73
74 // 都输出0
75 *gpfdat &= ~(1<<4);
76 *gpfdat &= ~(1<<5);
77 *gpfdat &= ~(1<<6);
78
79 down(&leds_lock);
80 leds_status = 0x0;
81 up(&leds_lock);
82 printk("/dev/leds open!\n");
83
84 }break;
85
86 case 1: /* /dev/led1 */
87 {
88 s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
89 s3c2410_gpio_setpin(S3C2410_GPF4, 0);
90
91 down(&leds_lock);
92 leds_status &= ~(1<<0);
93 up(&leds_lock);
94
95 break;
96 }
97
98 case 2: /* /dev/led2 */
99 {
100 s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);
101 s3c2410_gpio_setpin(S3C2410_GPF5, 0);
102 leds_status &= ~(1<<1);
103 break;
104 }
105
106 case 3: /* /dev/led3 */
107 {
108 s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);
109 s3c2410_gpio_setpin(S3C2410_GPF6, 0);
110
111 down(&leds_lock);
112 leds_status &= ~(1<<2);
113 up(&leds_lock);
114
115 break;
116 }
117 default:
118 break;
119
120 }
121
122 return 0;
123 }
124
125 static int s3c24xx_leds_read(struct file *filp, char __user *buff,
126 size_t count, loff_t *offp)
127 {
128 int minor = MINOR(filp->f_inode->i_rdev);
129 char val;
130 long ret;
131
132 switch (minor)
133 {
134 case 0: /* /dev/leds */
135 {
136
137 ret = copy_to_user(buff, (const void *)&leds_status, 1);
138 break;
139 }
140
141 case 1: /* /dev/led1 */
142 {
143 down(&leds_lock);
144 val = leds_status & 0x1;
145 up(&leds_lock);
146 ret = copy_to_user(buff, (const void *)&val, 1);
147 break;
148 }
149
150 case 2: /* /dev/led2 */
151 {
152 down(&leds_lock);
153 val = (leds_status>>1) & 0x1;
154 up(&leds_lock);
155 ret = copy_to_user(buff, (const void *)&val, 1);
156 break;
157 }
158
159 case 3: /* /dev/led3 */
160 {
161 down(&leds_lock);
162 val = (leds_status>>2) & 0x1;
163 up(&leds_lock);
164 ret = copy_to_user(buff, (const void *)&val, 1);
165 break;
166 }
167
168 }
169
170 return 1;
171 }
172
173 static ssize_t s3c24xx_leds_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
174 {
175 int minor = MINOR(file->f_inode->i_rdev);
176 char val;
177
178 long ret = 0;
179 ret = copy_from_user(&val, buf, 1);
180 if(ret > 0)
181 {
182 printk("s3c24xx_leds_write():copy_from_user fail! ret:%ld", ret);
183 return -EFAULT;
184 }
185
186 switch (minor)
187 {
188 case 0: /* /dev/leds */
189 {
190 if(val == 0)//点灯
191 {
192 printk("led_write led on\n");
193 *gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
194 }
195 else//灭灯
196 {
197 printk("led_write led off\n");
198 *gpfdat |= ((1<<4) | (1<<5) | (1<<6));
199 }
200
201
202 down(&leds_lock);
203 leds_status = val;
204 up(&leds_lock);
205 printk("/dev/leds write val:%d!\n", val);
206 break;
207 }
208
209 case 1: /* /dev/led1 */
210 {
211 s3c2410_gpio_setpin(S3C2410_GPF4, val);
212
213 if (val == 0)
214 {
215 down(&leds_lock);
216 leds_status &= ~(1<<0);
217 up(&leds_lock);
218 }
219 else
220 {
221 down(&leds_lock);
222 leds_status |= (1<<0);
223 up(&leds_lock);
224 }
225 break;
226 }
227
228 case 2: /* /dev/led2 */
229 {
230 s3c2410_gpio_setpin(S3C2410_GPF5, val);
231 if (val == 0)
232 {
233 down(&leds_lock);
234 leds_status &= ~(1<<1);
235 up(&leds_lock);
236 }
237 else
238 {
239 down(&leds_lock);
240 leds_status |= (1<<1);
241 up(&leds_lock);
242 }
243 break;
244 }
245
246 case 3: /* /dev/led3 */
247 {
248 s3c2410_gpio_setpin(S3C2410_GPF6, val);
249 if (val == 0)
250 {
251 down(&leds_lock);
252 leds_status &= ~(1<<2);
253 up(&leds_lock);
254 }
255 else
256 {
257 down(&leds_lock);
258 leds_status |= (1<<2);
259 up(&leds_lock);
260 }
261 break;
262 }
263
264 }
265
266 return 1;
267 }
268
269
270 /* 这个结构是字符设备驱动程序的核心
271 * 当应用程序操作设备文件时所调用的open、read、write等函数,
272 * 最终会调用这个结构中指定的对应函数
273 */
274 static struct file_operations s3c24xx_leds_fops = {
275 .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
276 .open = s3c24xx_leds_open,
277 .read = s3c24xx_leds_read,
278 .write = s3c24xx_leds_write,
279 };
280
281 /*
282 * 执行insmod命令时就会调用这个函数
283 */
284 static int __init s3c24xx_leds_init(void)
285 {
286 int minor = 0;
287 int result = 0;
288 dev_t devid;
289
290 major = LED_MAJOR;
291
292 devid = MKDEV(major, 0);//从主设备号major,次设备号0,得到dev_t类型
293 if(major)
294 {
295 result = register_chrdev_region(devid, LED_COUNT, "leds");//注册字符设备
296 minor = MINOR(devid);
297 }
298 else
299 {
300 result = alloc_chrdev_region(&devid, 0, LED_COUNT, "leds");//注册字符设备
301 major = MAJOR(devid); //从dev_t类型得到主设备
302 minor = MINOR(devid);
303 }
304 printk("led major=%d, minor=%d\r\n", major, minor);
305
306 cdev_init(&led_cdev, &s3c24xx_leds_fops);
307 cdev_add(&led_cdev, devid, LED_COUNT);
308 leds_class = class_create(THIS_MODULE, "myleds");//在/sys/class/下创建类目录
309 if (IS_ERR(leds_class))
310 {
311 printk("class_create is error!\n");
312 return PTR_ERR(leds_class);
313 }
314 printk("create leds class ok!\n");
315 //在/dev目录下创建设备节点
316 leds_class_devs[0] = device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds");
317 printk("create device leds ok!\n");
318
319 for (minor = 1; minor <4; minor++)
320 {
321 leds_class_devs[minor] = device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);
322 printk("create led%d ok!\n", minor);
323 if (unlikely(IS_ERR(leds_class_devs[minor])))
324 {
325 printk("unlikely is error!\n");
326 return PTR_ERR(leds_class_devs[minor]);
327 }
328 }
329
330 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //映射到虚拟地址
331 gpfdat = gpfcon + 1;
332
333 printk(DEVICE_NAME " initialized\n");
334 return 0;
335 }
336
337 /*
338 * 执行rmmod命令时就会调用这个函数
339 */
340 static void __exit s3c24xx_leds_exit(void)
341 {
342 int minor;
343 /* 卸载驱动程序 */
344
345 //1.销毁设备
346 for (minor = 0; minor <4; minor++)
347 {
348 device_destroy(leds_class, MKDEV(LED_MAJOR, minor));
349 }
350 //2.销毁类
351 class_destroy(leds_class);
352 //3.销毁cdev
353 cdev_del(&led_cdev);
354
355
356 unregister_chrdev_region(MKDEV(LED_MAJOR, 0), LED_COUNT);
357 iounmap(gpfcon);
358
359 }
360
361 /* 这两行指定驱动程序的初始化函数和卸载函数 */
362 module_init(s3c24xx_leds_init);
363 module_exit(s3c24xx_leds_exit);
364
365 /* 描述驱动程序的一些信息,不是必须的 */
366 MODULE_AUTHOR("jz2440");
367 MODULE_VERSION("0.1.0");
368 MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
369 MODULE_LICENSE("GPL");


View Code

 测试代码:


技术分享图片技术分享图片

1 #include
2 #include
3 #include
4 #include
5
6 /*
7 * ledtest
8 */
9
10 void print_usage(char *file)
11 {
12 printf("Usage:\n");
13 printf("%s \n",file);
14 printf("eg. \n");
15 printf("%s /dev/leds on\n", file);
16 printf("%s /dev/leds off\n", file);
17 printf("%s /dev/led1 on\n", file);
18 printf("%s /dev/led1 off\n", file);
19 }
20
21 int main(int argc, char **argv)
22 {
23 int fd;
24 char* filename;
25 char val;
26
27 if (argc != 3)
28 {
29 print_usage(argv[0]);
30 return 0;
31 }
32
33 filename = argv[1];
34
35 fd = open(filename, O_RDWR);
36 if (fd <0)
37 {
38 printf("error, can‘t open %s\n", filename);
39 return 0;
40 }
41
42 if (!strcmp("on", argv[2]))
43 {
44 // 亮灯
45 val = 0;
46 write(fd, &val, 1);
47 }
48 else if (!strcmp("off", argv[2]))
49 {
50 // 灭灯
51 val = 1;
52 write(fd, &val, 1);
53 }
54 else
55 {
56 print_usage(argv[0]);
57 return 0;
58 }
59
60
61 return 0;
62 }


View Code

本驱动程序可实现对3个LED灯整体控制和单个控制。仔细分析该代码有利于理解linux对于字符驱动实现方式及架构的理解。

本文主要分析驱动代码中的两个关键函数s3c24xx_leds_init()和s3c24xx_leds_exit()。

1、注册字符设备

内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。

(1)register_chrdev 比较老的内核注册的形式   早期的驱动

(2)register_chrdev_region/alloc_chrdev_region + cdev  新的驱动形式,需要配合下面两个函数使用:


cdev_init(&led_cdev, &s3c24xx_leds_fops);
cdev_add(
&led_cdev, devid, LED_COUNT);

区别: register_chrdev函数是老版本里面的设备号注册函数,可以实现静态和动态注册两种方法,主要是通过给定的主设备号是否为0来进行区别,为0的时候为动态注册。register_chrdev_region以及alloc_chrdev_region就是将上述函数的静态和动态注册设备号进行了拆分的强化。

1.1 register_chrdev_region

静态注册方法,即已知需要注册的设备号,通过调用该函数进行注册。

函数原型:


int register_chrdev_region(dev_t from, unsigned count, const char *name);

from:设备编号。
count:连续编号范围,是这组设备号的大小(也是次设备号的个数)。
name:编号相关联的设备名称. (/proc/devices); 本组设备的驱动名称。

对应卸载函数:


void unregister_chrdev_region(dev_t from, unsigned count)

 

1.2 alloc_chrdev_region

动态注册方法。

函数原型


int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

  dev:获得一个分配到的设备,可以用MAJOR宏和MINOR宏,将主设备号和次设备号提取出来。
  baseminor:次设备号的基准,从第几个次设备号开始分配。
  count:次设备号的个数。
  name:驱动的名字。

  说明:register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去创建他对应的设备文件。

  对应卸载函数:


void unregister_chrdev_region(dev_t from, unsigned count)

 

  1.3 cdev_init初始化

  执行注册后,需要实现cdev对象的初始化。以下是cdev结构的定义


struct cdev {
struct kobject kobj; // 每个 cdev 都是一个 kobject
struct module *owner; // 指向实现驱动的模块
const struct file_operations *ops; // 操纵这个字符设备文件的方法
struct list_head list; // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev; // 起始设备编号
unsigned int count; // 设备范围号大小
};

 

  本驱动代码使用的是静态内存定义的方式,使用以下两个函数。

  1)、cdev_init 初始化函数

  从以下代码中可见,该函数一方面对cdev的内部对象进行了初始化,另一方面,把fops与cdev进行了绑定。


/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev,
0, sizeof *cdev);
INIT_LIST_HEAD(
&cdev->list);
kobject_init(
&cdev->kobj, &ktype_cdev_default);
cdev
->ops = fops;
}

 

  2)、cdev_add函数

  该函数将p加入到当前系统中,并使其立即生效。如果失败,将返回一个负的错误码。


/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
int error;
p
->dev = dev;
p
->count = count;
error
= kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p
->kobj.parent);
return 0;
}

 

  需要说明的是,无论是动态注册还是静态注册,都是需要通过设备号获取设备在设备表中的位置(一个dev_t类型变量),该变量在后续会用的到。

  获取该设备号的方法如下:


MKDEV(MAJOR, MINOR);

  MKDEV是一个宏,返回一个dev_t类型数据;

  MAJOR:主设备号

  MINOR:次设备号

 

  2.创建设备节点

  2.1 手动创建

  在开发板执行insmod命令后,在执行"mknod /dev/led c 252 0",这样就创建出了主设备号252,次设备号0的/dev/led这个设备。

  2.2自动创建

  通过调用class_creat和device_create分别创建类和设备文件。

  调用class_creat这个宏会在/sys/class目录下创建类目录,如下图所示,创建了myleds目录,并创建了4个连接。

技术分享图片

 


/* This is a #define to keep the compiler from merging different
* instances of the __key variable
*/
#define class_create(owner, name) \
({
static struct lock_class_key __key; __class_create(owner, name, &__key); })

  owner:一般使用 THIS_MODULE

  name:本驱动使用“myleds”

  __class_create函数实现可参见 \linux-4.12.9\drivers\base\class.c

 

  调用device_create函数将在/dev目录下创建对应的设备节点。

  效果如下:

技术分享图片

 

   有以下注释可知,该函数在sysfs中创建了一个device。注意,调用该函数前,必须先创建一个类。


/**
* device_create - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device‘s name
*
* This function can be used by char device classes. A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in, the newly created
* struct device will be a child of that device in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Returns &struct device pointer on success, or ERR_PTR() on error.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt,
void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev
= device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}

 

  对应的卸载函数:


/**
* device_destroy - removes a device that was created with device_create()
* @class: pointer to the struct class that this device was registered with
* @devt: the dev_t of the device that was previously registered
*
* This call unregisters and cleans up a device that was created with a
* call to device_create().
*/
void device_destroy(struct class *class, dev_t devt);

 


/**
* class_destroy - destroys a struct class structure
* @cls: pointer to the struct class that is to be destroyed
*
* Note, the pointer to be destroyed must have been created with a call
* to class_create().
*/
void class_destroy(struct class *cls)

 

  3. ioremap

  几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址。根据CPU体系结构的不同,CPU对IO端口的编址方式有两种:

a -- I/O 映射方式(I/O-mapped)
    典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元。

b -- 内存映射方式(Memory-mapped)
    RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,外设I/O端口成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。

  一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。

  Linux在io.h头文件中声明了函数ioremap,用来将I/O内存资源的物理地址映射到核心虚地址空间(3GB-4GB)中(这里是内核空间),原型如下:


static inline void __iomem *ioremap(phys_addr_t offset, size_t size)

  phys_addr:要映射的起始的IO地址
  size:要映射的空间的大小

   对应卸载函数:


void iounmap(void * addr);

  内核代码中关于这个函数挺复杂,看了下最终应该会调用到__iounmap这个函数,mmu这部分后面再看了。这里应该知道在驱动卸载时调用该函数。

 

  总结:以上介绍了字符驱动安装和退出相关的函数,以及他们在linux系统的对应的行为。需要留意的一点是,无论init还是exit时对应的函数调用,都需要遵循一定的顺序。并且init和exit的顺序刚好是相反的。

  字符驱动初始化:

  1、注册字符驱动,cdev设备初始化。

  2、创建对应的class。

  3、创建设备device。

  4、调用ioremap进行内存空间到物理空间地址的映射。

无论设备个数,类作为对设备的抽象,只有一个。但是表现在内核中/sys/class/classname的类对象实际上是由device_create函数创建的。在本例中就创建了4个led设备。

 

 

 

 

 


推荐阅读
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文内容为asp.net微信公众平台开发的目录汇总,包括数据库设计、多层架构框架搭建和入口实现、微信消息封装及反射赋值、关注事件、用户记录、回复文本消息、图文消息、服务搭建(接入)、自定义菜单等。同时提供了示例代码和相关的后台管理功能。内容涵盖了多个方面,适合综合运用。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
author-avatar
贱男人少勾引天d_483
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有