1.Linux電源管理-休眠與喚醒

来源:https://www.cnblogs.com/lifexy/archive/2018/09/11/9629699.html
-Advertisement-
Play Games

1.休眠方式 在內核中,休眠方式有很多種,可以通過下麵命令查看 常用的休眠方式有freeze,standby, mem, disk freeze: 凍結I/O設備,將它們置於低功耗狀態,使處理器進入空閑狀態,喚醒最快,耗電比其它standby, mem, disk方式高 standby:除了凍結I/ ...


1.休眠方式

 在內核中,休眠方式有很多種,可以通過下麵命令查看

# cat /sys/power/state
          //來得到內核支持哪幾種休眠方式. 

 

常用的休眠方式有freeze,standby, mem, disk

  • freeze:   凍結I/O設備,將它們置於低功耗狀態,使處理器進入空閑狀態,喚醒最快,耗電比其它standby, mem, disk方式高
  • standby:除了凍結I/O設備外,還會暫停系統,喚醒較快,耗電比其它 mem, disk方式高
  • mem:      將運行狀態數據存到記憶體,並關閉外設,進入等待模式,喚醒較慢,耗電比disk方式高
  • disk:       將運行狀態數據存到硬碟,然後關機,喚醒最慢

示例:

 # echo standby > /sys/power/state
                   // 命令系統進入standby休眠.

 

2.喚醒方式

當我們休眠時,如果想喚醒,則需要添加中斷喚醒源,使得在休眠時,這些中斷是設為開啟的,當有中斷來,則會退出喚醒,常見的中斷源有按鍵,USB等.

 

3.以按鍵驅動為例(基於內核3.10.14)

在內核中,有個input按鍵子系統"gpio-keys"(位於driver/input/keyboard/gpio.keys.c),該平臺驅動platform_driver已經在內核中寫好了(後面會簡單分析)

我們只需要在內核啟動時,註冊"gpio-keys"平臺設備platform_device,即可實現一個按鍵驅動.

 

3.1首先使板卡支持input按鍵子系統(基於mips君正X1000的板卡)

查看Makefile,找到driver/input/keyboard/gpio.keys.c需要CONFIG_KEYBOARD_GPIO巨集

方式1-修改對應板卡的defconfig文件,添加巨集:

CONFIG_INPUT=y                             //支持input子系統(載入driver/input文件)
CONFIG_INPUT_KEYBOARD=y              //支持input->keyboards(載入driver/input/keyboard文件)
CONFIG_KEYBOARD_GPIO=y                   //支持input->keyboards->gpio按鍵(載入gpio.keys.c)

方式2-進入make menuconfig

-> Device Drivers                                                                                                   
   -> Input device support        
     ->  [*]Keyboards   
             [*]  GPIO Buttons

 

3.2修改好後,接下來寫my_button.c文件,來註冊platform_device

#include <linux/platform_device.h>
#include <linux/gpio_keys.h>
#include <linux/input.h>

struct gpio_keys_button __attribute__((weak)) board_buttons[] = {
         {
                  .gpio                 = GPIO_PB(31),    //按鍵引腳
                  .code   = KEY_POWER,                    //用來定義按鍵產生事件時,要上傳什麼按鍵值
                  .desc                 = "power key",    //描述信息,不填的話會預設設置為"gpio-keys"
                  .wakeup           =1,                   //設置為喚醒源
                  . debounce_interval =10,                //設置按鍵防抖動時間,也可以不設置
                  .type                = EV_KEY,
                  .active_low     = 1,                   //低電平有效
         },
};

static struct gpio_keys_platform_data  board_button_data = {
         .buttons  = board_buttons,
         .nbuttons         = ARRAY_SIZE(board_buttons),
};

struct platform_device  my_button_device = {
         .name               = "gpio-keys",
         .id             = -1,
         .num_resources      = 0,          
         .dev          = {
                .platform_data      = &board_button_data,
         }
};

static int __init button_base_init(void)
{
         platform_device_register(&my_button_device);
         return 0;
}

arch_initcall(button_base_init);     

上面的arch_initcall()表示:

會將button_base_init函數放在內核鏈接腳本.initcall3.init段中,然後在內核啟動時,會去讀鏈接腳本,然後找到button_base_init()函數,並執行它.

通常,在內核中,platform 設備的初始化(註冊)用arch_initcall()調用

而驅動的註冊則用module_init()調用,因為module_init()在arch_initcall()之後才調用

因為在init.h中定義:

#define pure_initcall(fn)                  __define_initcall(fn, 0)

#define core_initcall(fn)                  __define_initcall(fn, 1)

#define core_initcall_sync(fn)                __define_initcall(fn, 1s)

#define postcore_initcall(fn)          __define_initcall(fn, 2)

#define postcore_initcall_sync(fn)        __define_initcall(fn, 2s)

#define arch_initcall(fn)         __define_initcall(fn, 3)            // arch_initcall()優先順序為3,比module_init()先執行

#define arch_initcall_sync(fn)                 __define_initcall(fn, 3s)

#define subsys_initcall(fn)              __define_initcall(fn, 4)

#define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)

#define fs_initcall(fn)                       __define_initcall(fn, 5)

#define fs_initcall_sync(fn)             __define_initcall(fn, 5s)

#define rootfs_initcall(fn)               __define_initcall(fn, rootfs)

#define device_initcall(fn)              __define_initcall(fn, 6)                    //module_init()優先順序為6

#define device_initcall_sync(fn)    __define_initcall(fn, 6s)

#define late_initcall(fn)          __define_initcall(fn, 7)

#define late_initcall_sync(fn)                  __define_initcall(fn, 7s)

... ...
#define __initcall(fn) device_initcall(fn) #define module_init(x) __initcall(fn)         //module_init 等於 device_initcall

 

3.3然後將my_button.c文件添加到Makefile中

編譯內核後,便實現一個簡單的按鍵喚醒休眠了.

 

接下來開始分析platform_driver(位於driver/input/keyboard/gpio.keys.c),看看是如何註冊按鍵和實現喚醒的.

 

4.分析driver/input/keyboard/gpio.keys.c

4.1該文件里有常用的函數有

static int gpio_keys_probe(struct platform_device *pdev);        

設置按鍵和input_dev,註冊input-key子系統

 

static int gpio_keys_setup_key(struct platform_device *pdev,struct input_dev *input, struct gpio_button_data *bdata,const struct gpio_keys_button *button);

設置GPIO,設置input結構體支持的按鍵值,設置中斷,設置防抖動機制

 

static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id);

按鍵中斷函數,如果是中斷源,則通過pm_stay_awake()通知pm子系統喚醒,如果有防抖動,則延時並退出,否則通過schedule_work()來調用gpio_keys_gpio_work_func()一次

 

static void gpio_keys_gpio_timer(unsigned long _data);

定時器超時處理函數,用來實現防抖動,裡面會通過schedule_work()來調用一次gpio_keys_gpio_work_func();

 

static void gpio_keys_gpio_work_func(struct work_struct *work);

處理gpio事件函數,用來上報input事件,並判斷按鍵中斷源,如果是的話,則調用pm_relax(),通知pm子系統喚醒工作結束

 

void pm_wakeup_event(struct device *dev, unsigned int msec);

通知pm(power manager), 喚醒休眠

 

static int gpio_keys_suspend(struct device *dev);

休眠函數,休眠之前會被調用

 

static int gpio_keys_resume(struct device *dev);

喚醒函數,喚醒之前被調用

 

static SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend, gpio_keys_resume);

SIMPLE_DEV_PM_OPS巨集位於pm.h,它將會定義一個dev_pm_ops結構體,用來被pm子系統調用,實現休眠喚醒

 

4.2 首先來看probe函數

如下圖所示,probe函數為gpio_keys_probe()

 

gpio_keys_probe()函數定義如下所示:

static int gpio_keys_probe(struct platform_device *pdev)
{

struct device *dev = &pdev->dev;                           //獲取平臺設備的.dev
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);   //獲取my_button.c文件的board_button_data成員
struct gpio_keys_drvdata *ddata;                          //按鍵驅動數據
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);      //獲取平臺匯流排設備數據

if (!pdata) {
               pdata = gpio_keys_get_devtree_pdata(dev);
                  if (IS_ERR(pdata))
                          return PTR_ERR(pdata);}

ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
                          pdata->nbuttons * sizeof(struct gpio_button_data),
                          GFP_KERNEL);                                          //給平臺設備數據分配空間

input = input_allocate_device();                        //分配input 按鍵子系統
         if (!ddata || !input) {
                  dev_err(dev, "failed to allocate state\n");
                  error = -ENOMEM;
                  goto fail1;
         }

 

ddata->pdata = pdata;                                     
ddata->input = input;
mutex_init(&ddata->disable_lock);

platform_set_drvdata(pdev, ddata);             
//將ddata保存到平臺匯流排設備的私有數據。以後只需調用platform_get_drvdata()就能獲取驅動數據
//設置pdev->dev->p結構體成員下的數據
//令pdev->dev->p->device = &pdev->dev
//pdev-> dev->p->driver_data = ddata

input_set_drvdata(input, ddata);
//將ddata保存到要註冊的按鍵子系統驅動的私有數據中。以後只需調用input_get_drvdata()就能獲取驅動數據
//設置input ->dev->p結構體成員下的數據
//令input ->dev->p->device = &pdev->dev
// input ->p->driver_data = ddata

         input->name = pdata->name ? : pdev->name;           //等於"gpio-keys"
         input->phys = "gpio-keys/input0";           //input 按鍵子系統處於/sys目錄下的哪個路徑
         input->dev.parent = &pdev->dev;
         input->open = gpio_keys_open;             //input打開操作,用來正常喚醒調用
         input->close = gpio_keys_close;            //input關閉操作,用來正常休眠調用

         input->id.bustype = BUS_HOST;             //設置匯流排
         input->id.vendor = 0x0001;
         input->id.product = 0x0001;
         input->id.version = 0x0100;

         /* Enable auto repeat feature of Linux input subsystem */
         if (pdata->rep)
        __set_bit(EV_REP, input->evbit);      
         for (i = 0; i < pdata->nbuttons; i++) {
                  const struct gpio_keys_button *button = &pdata->buttons[i];   //獲取每個按鍵
                  struct gpio_button_data *bdata = &ddata->data[i];

                  error = gpio_keys_setup_key(pdev, input, bdata, button);// gpio_keys_setup_key()里會執行:
                           //賦值按鍵,使 bdata->button = button
                            //通過gpio_request_one()來申請button->gpio管腳為輸入模式
                           //判斷 button->debounce_interval成員,是否設置防抖動時間
                           //獲取按鍵對應的中斷號,並賦值給bdata->irq
                            //通過__set_bit()來讓input 按鍵子系統支持button->code
                           //通過setup_timer()設置bdata->timer結構體對應的超時函數
                           //通過request_any_context_irq()函數註冊按鍵中斷:
                              // ----> 中斷號為bdata->irq,中斷名叫: button.desc("power key")
                              // ----> 中斷標誌位為(IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
                              // ----> 中斷服務函數為gpio_keys_gpio_isr(),設置中斷函數參數dev_id為bdata

                  if (error)
                          goto fail2;

                  if (button->wakeup)                 //判斷該按鍵是否是喚醒源
                          wakeup = 1;            
         }

         error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);   //在/sys/devices/platform/gpio-keys下創建sys設備節點
         if (error) {
                  dev_err(dev, "Unable to export keys/switches, error: %d\n",
                          error);
                  goto fail2;
         }
         error = input_register_device(input);   //註冊input按鍵子系統
         if (error) {
                  dev_err(dev, "Unable to register input device, error: %d\n",
                          error);
                  goto fail3;
         }
         device_init_wakeup(&pdev->dev, wakeup);         //如果按鍵有設置喚醒源,則設置標誌位
}

 

4.3其中device_init_wakeup()函數定義如下:

static inline int device_init_wakeup(struct device *dev, bool val)
{
         device_set_wakeup_capable(dev, val);  //設置dev->power.can_wakeup = val;
         device_set_wakeup_enable(dev, val);  //設置dev->power.should_wakeup = val;
         return 0;
}

然後休眠喚醒的時候,就會根據dev->power.can_wakeupdev->power.should_wakeup來做不同的操作

 

4.4 其中gpio_keys_suspend()休眠函數定義如下所示:

static int gpio_keys_suspend(struct device *dev)
{
         struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);
         struct input_dev *input = ddata->input;
         int i;

         if (device_may_wakeup(dev)) //判斷dev->power.can_wakeup和dev->power.should_wakeup,是否有中斷源按鍵
      {               
                  for (i = 0; i < ddata->pdata->nbuttons; i++) //如果有,則遍歷每個按鍵
          {
                          struct gpio_button_data *bdata = &ddata->data[i];
                          if (bdata->button->wakeup)
                                   enable_irq_wake(bdata->irq);  //將要睡眠的中斷號屏蔽掉,實現休眠時保持中斷喚醒
                 }
         } 
      else
      { mutex_lock(&input->mutex); if (input->users) gpio_keys_close(input); //調用dev->platform_data-> disable成員函數 mutex_unlock(&input->mutex); } return 0; }

從上面函數可以看到,進入休眠之前,我們需要調用enable_irq_wake()來設置喚醒源

 

4.5 然後在中斷函數中,判斷是否需要上報喚醒事件,中斷函數如下所示:

static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
         struct gpio_button_data *bdata = dev_id;
         BUG_ON(irq != bdata->irq);

         if (bdata->button->wakeup)
                  pm_stay_awake(bdata->input->dev.parent);       //如果是喚醒源,則通知pm子系統,處理喚醒事件,並等待結束

         if (bdata->timer_debounce)
                  mod_timer(&bdata->timer,jiffies + msecs_to_jiffies(bdata->timer_debounce));//如果設置防抖動,則啟動定時器並退出

         else
                  schedule_work(&bdata->work);  //否則調用bdata->work對應的函數gpio_keys_gpio_work_func()
         return IRQ_HANDLED;
}

其中gpio_keys_gpio_work_func()函數如下所示:

static void gpio_keys_gpio_work_func(struct work_struct *work)
{
         struct gpio_button_data *bdata= container_of(work, struct gpio_button_data, work);  

         gpio_keys_gpio_report_event(bdata); //上傳input按鍵事件
        
     if (bdata->button->wakeup)       pm_relax(bdata->input->dev.parent); //如果是喚醒源,則通知pm子系統,喚醒中斷處理結束。 }

從上面兩個函數可以看到,喚醒休眠時,需要使用兩個函數實現:

pm_stay_awake();                   //在中斷入口調用,告知啟動喚醒
pm_relax();                        //在中斷出口調用,告知結束喚醒

在中斷前調用pm_stay_awake(),中斷結束時再調用一次pm_relax()函數.

 

4.6 如果想延時喚醒,也可以使用另一種喚醒休眠,則只需要一個函數實現:

pm_wakeup_event(struct device *dev, unsigned int msec);

         //通知pm子系統在msec後處理喚醒事件, msec=0,則表示立即喚醒

 

4.7 接下來來看gpio_keys_setup_key(),如何設置按鍵的(只加了重要的部分)

static int gpio_keys_setup_key(struct platform_device *pdev,
                                   struct input_dev *input,
                                   struct gpio_button_data *bdata,
                                   const struct gpio_keys_button *button)
{
         const char *desc = button->desc ? button->desc : "gpio_keys"; //獲取平臺設備設置的名字
         //… …
         error = gpio_request_one(button->gpio, GPIOF_IN, desc);//申請button->gpio引腳,並將引腳設為輸入引腳,名字設置為desc

         if (button->debounce_interval)    
         {
        bdata->timer_debounce =button->debounce_interval;      //設置防抖動時間 
      }  
      irq = gpio_to_irq(button->gpio);                     //獲取管腳對應的中斷號  
      if (irq < 0) 
      {  
//… …      goto fail;         }      bdata->irq = irq;   INIT_WORK(&bdata->work, gpio_keys_gpio_work_func);     //初始化bdata->work,使bdata->work與gpio_keys_gpio_work_func()函數關聯起來     //後面當調用schedule_work(&bdata->work)時,便會執行gpio_keys_gpio_work_func()函數        setup_timer(&bdata->timer, gpio_keys_gpio_timer, (unsigned long)bdata);   //設置gpio_keys_gpio_timer()定時器超時函數,用來實現防抖動,函數參數為bdata   irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;   //中斷標誌位   isr = gpio_keys_gpio_isr;   input_set_capability(input, button->type ?: EV_KEY, button->code); //使input 支持EV_KEY鍵盤事件,並使鍵盤事件支持button->code按鍵值 error = request_any_context_irq(bdata->irq, isr, irqflags, desc, bdata);   //通過request_any_context_irq()函數註冊按鍵中斷:         //中斷號為bdata->irq,中斷名叫: button.desc("power key")         //中斷標誌位為(IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)         //中斷服務函數為gpio_keys_gpio_isr(),設置中斷函數參數dev_id為bdata return 0;    }

 

通過gpio.keys.c,得出喚醒流程:

休眠時:

enable_irq_wake (bdata->irq);             
//將要睡眠的中斷號屏蔽掉,實現休眠時保持中斷喚醒

喚醒後:

disable_irq_wake(bdata->irq);      //關閉喚醒 

中斷時,有兩種喚醒PM模式

模式1-使用兩個函數實現:

  • 進入中斷時調用一次pm_stay_awake().
  • 退出時也調用一次pm_relax(bdata->input->dev.parent);

模式2-只需一個函數實現:

  • 進入中斷時調用pm_wakeup_event(struct device *dev, unsigned int msec).

 

5.接下來,我們自己寫個按鍵字元驅動,實現休眠喚醒

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/spinlock.h>
#include <soc/gpio.h>


#define MYKEY_GPIO GPIO_PB(31)
static DECLARE_WAIT_QUEUE_HEAD(mykey_waitqueue);

struct mykey_button {
         unsigned int gpio;           
         const char *desc;
         int wakeup;                                /*喚醒源*/             
         int debounce_interval;                     /* 防抖動 時間ms*/
         int wait_event;                            /*等待隊列事件*/
         int key_val;                               /*按鍵值*/
         int irq;
         struct timer_list timer;                   /*防抖動定時器*/
         struct work_struct work;
         struct device *dev;
};


static struct mykey_button mykey_data={
                  .gpio = MYKEY_GPIO,
                  .desc = "mykey",
                  .wakeup = 1,
                  .debounce_interval = 10,                //10ms
                  .wait_event = 0,
};

 

static void mykey_func(struct work_struct *work)
{

         struct mykey_button *data = container_of(work, struct mykey_button, work);  //通過work成員變數找到父結構體

         if(data->wakeup)
         {
                  pm_wakeup_event(data->dev, 0);
         }

         data->key_val =gpio_get_value(data->gpio);
         data->wait_event =1;
         wake_up_interruptible(&mykey_waitqueue);                        //喚醒隊列
}
static void mykey_irq_timer(unsigned long _data) { struct mykey_button *data =(struct mykey_button *)_data; schedule_work(&data->work); //調用mykey_func()函數 } static irqreturn_t mykey_irq(int irq, void *dev_id) { struct mykey_button *data = dev_id; if(data->debounce_interval) mod_timer(&data->timer, jiffies+msecs_to_jiffies(10)); else schedule_work(&data->work); return IRQ_HANDLED; } static ssize_t mykey_read(struct file *file, char __user *user, size_t count, loff_t *ppos) { wait_event_interruptible(mykey_waitqueue,mykey_data.wait_event ); //進入等待隊列休眠,如果中斷來數據,則跳出 copy_to_user(user, &mykey_data.key_val, sizeof(mykey_data.key_val)); mykey_data.wait_event =0; return 0; } static int mykey_open(struct inode *inode, struct file *file) { int err=0; int irq; err=gpio_request_one(mykey_data.gpio, GPIOF_DIR_IN, mykey_data.desc); //獲取管腳,並設置管腳為輸入 if (err < 0) { printk("mykey_open err : gpio_request_one err=%d\n",err); return -EINVAL; } irq = gpio_to_irq(mykey_data.gpio); //獲取IRQ中斷號,用來註冊中斷 if(irq<0) { err =irq; printk("mykey_open err : gpio_to_irq err=%d\n",irq); goto fail; } mykey_data.irq = irq; INIT_WORK(&mykey_data.work, mykey_func); //初始化工作隊列 err=request_irq(irq,mykey_irq,IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING, mykey_data.desc,&mykey_data); if (err) { printk("mykey_open err : request_irq err=%d\n",err); goto fail; } if(mykey_data.wakeup) enable_irq_wake(irq); //將引腳設為喚醒源 if(mykey_data.debounce_interval) setup_timer(&mykey_data.timer, mykey_irq_timer, (unsigned long)&mykey_data); //設置定時器 add_timer(&mykey_data.timer); return 0; fail: if (gpio_is_valid(mykey_data.gpio)) gpio_free(mykey_data.gpio); return err; }
static int mykey_release(struct inode *inode, struct file *file) { free_irq(mykey_data.irq,&mykey_data); cancel_work_sync(&mykey_data.work); if(mykey_data.wakeup) disable_irq_wake(mykey_data.irq); if(mykey_data.debounce_interval) del_timer_sync(&mykey_data.timer); gpio_free(mykey_data.gpio); return 0; } struct file_operations mykey_ops={ .owner = THIS_MODULE, .open = mykey_open, .read = mykey_read, .release=mykey_release, }; static int major; static struct class *cls; static int mykey_init(void) { struct device *mydev; major=register_chrdev(0,"mykey", &mykey_ops); cls=class_create(THIS_MODULE, "mykey"); mydev = device_create(cls, 0, MKDEV(major,0),&mykey_data,"mykey"); mykey_data.dev = mydev; return 0; } static void mykey_exit(void) { device_destroy(cls, MKDEV(major,0)); class_destroy(cls); unregister_chrdev(major, "mykey"); } module_init(mykey_init); module_exit(mykey_exit); MODULE_LICENSE("GPL");

應用測試代碼如下:

#include <sys/types.h>   
#include <sys/stat.h>    
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main(int argc,char **argv)
{
 int fd,ret;
 unsigned int val=0;              
 fd=open("/dev/mykey",O_RDWR);       
 if(fd<0)
     {printf("can't open!!!\n");
     return -1;}

 while(1)
 {
     ret=read(fd,&val,1);           //讀取一個值,(當在等待隊列時,本進程就會進入休眠狀態)
     if(ret<0)
     {
     printf("read err!\n");   
     continue;
     }
  printf("key_val=%d\r\n",val);
}
 return 0;
}

 試驗:

./mykey_text &              
 echo mem > /sys/power/state      //然後按GPB31對應的按鍵來喚醒休眠

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 互聯網+的需要 在信息越來越繁雜的互聯網時代,公司所運行的項目越來越多,項目相關服務繁多,服務之間存在複雜的依賴關係,運維與管理任務越來越繁重,手工交付需要花費很多的人力與時間,且安全性和時效性均無法保證。對於多資源型分佈/分離式部署項目,Udeployer應運而生。 隨著企業對版本上線質量和速度的 ...
  • 引子 平常工作中經常需要查看很大的文本文件,如果用vi打開的話會非常慢,所以平常都用less,但是並沒有很系統地學習過less的用法,今天總結一下less和more的用法。 經過學習我發現less比more更強大更易用。 more 用途: 分頁顯示大文本文件。 格式: more [-OPTION] ...
  • 版權聲明:本文為博主原創文章,未經允許不得轉載 引子 gzip是Linux系統中最常用也是高效的壓縮壓縮命令。早期Linux系統中主要使用compress命令壓縮,得到尾碼為“.Z”的壓縮文件,但是後來gzip被髮明出來替代了compress成為主流的壓縮命令。gzip有更好的壓縮比,而且能夠解壓“ ...
  • Redis安裝 下載地址:https://github.com/MicrosoftArchive/redis/releases 下載對應的版本:這裡下載Redis-x64-3.2.100 解壓文件 進入Redis-x64-3.2.100目錄,執行redis-server.exe redis.wind ...
  • Cmder是Windows下的命令行工具,用來代替Windows自帶的cmd 官網:http://cmder.net/ 這裡下載Full版本 https://github.com/cmderdev/cmder/releases/tag/v1.3.6 下載解壓打開即可使用 如何將Cmder添加到右鍵菜 ...
  • 最近看到OVS用戶態的代碼,在接收內核態信息的時候,使用了Epoll多路復用機制,對其十分不解,於是從網上找了一些資料,學習了一下《UNIX網路變成捲1:套接字聯網API》這本書對應的章節,網上雖然關於該主題的博文很多,並且講解的很詳細,但是在這裡還是做一個學習筆記,記錄一下自己的想法。 IO模型 ...
  • Fedora 28 本身是沒有列印服務的。我們需要安裝下列軟體: System-Config-Printer Common Unix Printing System - CUPS hplip.x86_64 : HP Linux Imaging and Printing Project hplip-3 ...
  • 一. 開篇說明 1. 關於Linux安裝MySQL, 設置伺服器防火牆查閱往期隨筆, 這裡不再贅述. 2. 本篇要求對資料庫有基本瞭解即可. 二. 設置遠程登錄用戶 1. 登錄到Linux伺服器mysql mysql -u root -p 2. 創建遠程登錄用戶和用戶名, 註意關於許可權和ip地址網段 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...