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

 

 

 

 

 


推荐阅读
  • 本文探讨了如何通过最小生成树(MST)来计算严格次小生成树。在处理过程中,需特别注意所有边权重相等的情况,以避免错误。我们首先构建最小生成树,然后枚举每条非树边,检查其是否能形成更优的次小生成树。 ... [详细]
  • 本文介绍如何使用 NSTimer 实现倒计时功能,详细讲解了初始化方法、参数配置以及具体实现步骤。通过示例代码展示如何创建和管理定时器,确保在指定时间间隔内执行特定任务。 ... [详细]
  • 本文介绍如何使用阿里云的fastjson库解析包含时间戳、IP地址和参数等信息的JSON格式文本,并进行数据处理和保存。 ... [详细]
  • QUIC协议:快速UDP互联网连接
    QUIC(Quick UDP Internet Connections)是谷歌开发的一种旨在提高网络性能和安全性的传输层协议。它基于UDP,并结合了TLS级别的安全性,提供了更高效、更可靠的互联网通信方式。 ... [详细]
  • 2023 ARM嵌入式系统全国技术巡讲旨在分享ARM公司在半导体知识产权(IP)领域的最新进展。作为全球领先的IP提供商,ARM在嵌入式处理器市场占据主导地位,其产品广泛应用于90%以上的嵌入式设备中。此次巡讲将邀请来自ARM、飞思卡尔以及华清远见教育集团的行业专家,共同探讨当前嵌入式系统的前沿技术和应用。 ... [详细]
  • 深入理解 Oracle 存储函数:计算员工年收入
    本文介绍如何使用 Oracle 存储函数查询特定员工的年收入。我们将详细解释存储函数的创建过程,并提供完整的代码示例。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文介绍如何在 Xcode 中使用快捷键和菜单命令对多行代码进行缩进,包括右缩进和左缩进的具体操作方法。 ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 本文介绍如何通过Windows批处理脚本定期检查并重启Java应用程序,确保其持续稳定运行。脚本每30分钟检查一次,并在需要时重启Java程序。同时,它会将任务结果发送到Redis。 ... [详细]
  • 本文介绍了在Windows环境下使用pydoc工具的方法,并详细解释了如何通过命令行和浏览器查看Python内置函数的文档。此外,还提供了关于raw_input和open函数的具体用法和功能说明。 ... [详细]
  • 深入理解OAuth认证机制
    本文介绍了OAuth认证协议的核心概念及其工作原理。OAuth是一种开放标准,旨在为第三方应用提供安全的用户资源访问授权,同时确保用户的账户信息(如用户名和密码)不会暴露给第三方。 ... [详细]
  • 国内BI工具迎战国际巨头Tableau,稳步崛起
    尽管商业智能(BI)工具在中国的普及程度尚不及国际市场,但近年来,随着本土企业的持续创新和市场推广,国内主流BI工具正逐渐崭露头角。面对国际品牌如Tableau的强大竞争,国内BI工具通过不断优化产品和技术,赢得了越来越多用户的认可。 ... [详细]
  • 本题涉及一棵由N个节点组成的树(共有N-1条边),初始时所有节点均为白色。题目要求处理两种操作:一是改变某个节点的颜色(从白变黑或从黑变白);二是查询从根节点到指定节点路径上的第一个黑色节点,若无则输出-1。 ... [详细]
  • Linux设备驱动程序:异步时间操作与调度机制
    本文介绍了Linux内核中的几种异步延迟操作方法,包括内核定时器、tasklet机制和工作队列。这些机制允许在未来的某个时间点执行任务,而无需阻塞当前线程,从而提高系统的响应性和效率。 ... [详细]
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社区 版权所有