热门标签 | 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设备。

 

 

 

 

 


推荐阅读
  • Startup 类配置服务和应用的请求管道。Startup类ASP.NETCore应用使用 Startup 类,按照约定命名为 Startup。 Startup 类:可选择性地包括 ... [详细]
  • 本文介绍了如何使用Java中的同步方法和同步代码块来实现两个线程的交替打印。一个线程负责打印1到52的数字,另一个线程负责打印A到Z的字母,确保输出顺序为12A34B...5152Z。 ... [详细]
  • 本文介绍如何在 C++ 中使用链表结构存储和管理数据。通过具体示例,展示了静态链表的基本操作,包括节点的创建、链接及遍历。 ... [详细]
  • 优化局域网SSH连接延迟问题的解决方案
    本文介绍了解决局域网内SSH连接到服务器时出现长时间等待问题的方法。通过调整配置和优化网络设置,可以显著缩短SSH连接的时间。 ... [详细]
  • 回顾2014年,我经历了多个重要项目和学习阶段,取得了一定的成绩。新的一年即将到来,希望能在更多项目实践中继续成长。 ... [详细]
  • HDU 1394:线段树优化求解逆序对问题
    本文介绍如何使用线段树高效求解排列中的逆序对问题。通过单点增减和区间求和操作,线段树能够快速处理此类问题,并提供了一种替代树状数组的解决方案。 ... [详细]
  • TechStride 网站
    TechStride 成立于2014年初,致力于互联网前沿技术、产品创意及创业内容的聚合、搜索、学习与展示。我们旨在为互联网从业者提供更高效的新技术搜索、学习、分享和产品推广平台。 ... [详细]
  • 本文详细探讨了 Django 的 ORM(对象关系映射)机制,重点介绍了其如何通过 Python 元类技术实现数据库表与 Python 类的映射。此外,文章还分析了 Django 中各种字段类型的继承结构及其与数据库数据类型的对应关系。 ... [详细]
  • 本文介绍了 Winter-1-C A + B II 问题的详细解题思路和测试数据。该问题要求计算两个大整数的和,并输出结果。我们将深入探讨如何处理大整数运算,确保在给定的时间和内存限制下正确求解。 ... [详细]
  • 本文详细介绍超文本标记语言(HTML)的基本概念与语法结构。HTML是构建网页的核心语言,通过标记标签描述页面内容,帮助开发者创建结构化、语义化的Web页面。 ... [详细]
  • 哈密顿回路问题旨在寻找一个简单回路,该回路包含图中的每个顶点。本文将介绍如何判断给定的路径是否构成哈密顿回路。 ... [详细]
  • JavaScript 中创建对象的多种方式
    本文介绍了 JavaScript 中创建对象的几种常见方法,包括字面量形式、构造函数、原型对象等。每种方法都有其特点和适用场景,通过对比分析,帮助开发者选择最适合的方式。 ... [详细]
  • 本文探讨了在使用Selenium进行自动化测试时,由于webdriver对象实例化位置不同而导致浏览器闪退的问题,并提供了详细的代码示例和解决方案。 ... [详细]
  • 算法题解析:最短无序连续子数组
    本题探讨如何通过单调栈的方法,找到一个数组中最短的需要排序的连续子数组。通过正向和反向遍历,分别使用单调递增栈和单调递减栈来确定边界索引,从而定位出最小的无序子数组。 ... [详细]
  • 本文深入探讨了线性代数中向量的线性关系,包括线性相关性和极大线性无关组的概念。通过分析线性方程组和向量组的秩,帮助读者理解这些概念在实际问题中的应用。 ... [详细]
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社区 版权所有