百篇博客系列篇.本篇为:

进程通讯相关篇为:

本篇说清楚互斥锁

内核中哪些地方会用到互斥锁?看图:

图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能.

概述

自旋锁互斥锁 虽都是锁,但解决的问题不同, 自旋锁解决用于CPU核间共享内存的竞争,而互斥锁解决线程(任务)间共享内存的竞争.

自旋锁的特点是死守共享资源,拿不到锁,CPU选择忙等(busy waiting),等待其他CPU释放资源.所以共享代码段不能太复杂,否则容易死锁,休克.

互斥锁的特点是拿不到锁往往原任务阻塞,切换到新任务运行.CPU是会一直跑的.这样很容易会想到几个问题:

第一:会出现很多任务在等同一把锁的情况出现,因为切换新任务也可能因要同一把锁而被阻塞,CPU又被调去跑新新任务了.这样就会出现一个等锁的链表.

第二:持有锁的一方再申请同一把锁时还能成功吗? 答案是可以的,这种锁叫递归锁,是鸿蒙内核默认方式.

第三:当优先级很高的A任务要锁失败,主动让出CPU进入睡眠,而如果持有锁的B任务优先级很低, 迟迟等不到调度不到B任务运行,无法释放锁怎么办?

答案是会临时调整B任务的优先级,调到A一样高,这样B能很快的被调度到,等B释放锁后其优先级又会被打回原形.所以一个任务的优先级会看情况时高时低.

第四:B任务释放锁之后要主动唤醒等锁的任务链表,使他们能加入就绪队列,等待被调度.调度算法是一视同仁的,它只看优先级.

带着这些问题,进入鸿蒙内核互斥锁的实现代码,本篇代码量较大, 每行代码都一一注解说明.

互斥锁长什么样?

  1. enum {
  2. LOS_MUX_PRIO_NONE = 0 //线程的优先级和调度不会受到互斥锁影响,先来后到,普通排队.
  3. LOS_MUX_PRIO_INHERIT = 1 //当高优先级的等待低优先级的线程释放锁时,低优先级的线程以高优先级线程的优先级运行。
  4. //当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级
  5. LOS_MUX_PRIO_PROTECT = 2 //详见:OsMuxPendOp中的注解,详细说明了LOS_MUX_PRIO_PROTECT的含义
  6. };
  7. enum {
  8. LOS_MUX_NORMAL = 0 //非递归锁 只有[0.1]两个状态,不做任何特殊的错误检,不进行deadlock detection(死锁检测)
  9. LOS_MUX_RECURSIVE = 1 //递归锁 允许同一线程在互斥量解锁前对该互斥量进行多次加锁。递归互斥量维护锁的计数,在解锁次数和加锁次数不相同的情况下,不会释放锁,别的线程就无法加锁此互斥量。
  10. LOS_MUX_ERRORCHECK = 2 //进行错误检查,如果一个线程企图对一个已经锁住的mutex进行relock或对未加锁的unlock,将返回一个错误。
  11. LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE //鸿蒙系统默认使用递归锁
  12. };
  13. typedef struct { //互斥锁的属性
  14. UINT8 protocol; //协议
  15. UINT8 prioceiling; //优先级上限
  16. UINT8 type; //类型属性
  17. UINT8 reserved; //保留字段
  18. } LosMuxAttr;
  19. typedef struct OsMux { //互斥锁结构体
  20. UINT32 magic; /**< magic number */ //魔法数字
  21. LosMuxAttr attr; /**< Mutex attribute */ //互斥锁属性
  22. LOS_DL_LIST holdList; /**< The task holding the lock change */ //当有任务拿到本锁时,通过holdList节点把锁挂到该任务的锁链表上
  23. LOS_DL_LIST muxList; /**< Mutex linked list */ //等这个锁的任务链表,上面挂的都是任务,注意和holdList的区别.
  24. VOID *owner; /**< The current thread that is locking a mutex */ //当前拥有这把锁的任务
  25. UINT16 muxCount; /**< Times of locking a mutex */ //锁定互斥体的次数,递归锁允许多次
  26. } LosMux;

这互斥锁长的明显的比自旋锁丰满多啦,还记得自旋锁的样子吗,就一个变量,单薄到令人心疼.

初始化

  1. LITE_OS_SEC_TEXT UINT32 LOS_MuxInit(LosMux *mutex const LosMuxAttr *attr)
  2. { //...
  3. SCHEDULER_LOCK(intSave); //拿到调度自旋锁
  4. mutex->muxCount = 0; //锁定互斥量的次数
  5. mutex->owner = NULL; //持有该锁的任务
  6. LOS_ListInit(&mutex->muxList); //初始化等待该锁的任务链表
  7. mutex->magic = OS_MUX_MAGIC; //固定标识,互斥锁的魔法数字
  8. SCHEDULER_UNLOCK(intSave); //释放调度自旋锁
  9. return LOS_OK;
  10. }

留意mutex->muxList,这又是一个双向链表, 双向链表是内核最重要的结构体,不仅仅是鸿蒙内核,在linux内核中(list_head)又何尝不是,牢牢的寄生在宿主结构体上.muxList上挂的是未来所有等待这把锁的任务.

三种申请模式

申请互斥锁有三种模式:无阻塞模式、永久阻塞模式、定时阻塞模式。

无阻塞模式:即任务申请互斥锁时,入参timeout等于0。若当前没有任务持有该互斥锁,或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务,则申请成功,否则立即返回申请失败。

永久阻塞模式:即任务申请互斥锁时,入参timeout等于0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则,任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。

定时阻塞模式:即任务申请互斥锁时,0<timeout<0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该互斥锁,则该任务可成功获取互斥锁继续执行,若超时前未获取到该互斥锁,接口将返回超时错误码。

如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。

如果没有任务阻塞于该互斥锁,则互斥锁释放成功。

申请互斥锁主函数 OsMuxPendOp

  1. //互斥锁的主体函数,由OsMuxlockUnsafe调用,互斥锁模块最重要的几个函数之一
  2. //最坏情况就是拿锁失败,让出CPU,变成阻塞任务,等别的任务释放锁后排到自己了接着执行.
  3. STATIC UINT32 OsMuxPendOp(LosTaskCB *runTask LosMux *mutex UINT32 timeout)
  4. {
  5. UINT32 ret;
  6. LOS_DL_LIST *node = NULL;
  7. LosTaskCB *owner = NULL;
  8. if ((mutex->muxList.pstPrev == NULL) || (mutex->muxList.pstNext == NULL)) {//列表为空时的处理
  9. /* This is for mutex macro initialization. */
  10. mutex->muxCount = 0;//锁计数器清0
  11. mutex->owner = NULL;//锁没有归属任务
  12. LOS_ListInit(&mutex->muxList);//初始化锁的任务链表,后续申请这把锁任务都会挂上去
  13. }
  14. if (mutex->muxCount == 0) {//无task用锁时,肯定能拿到锁了.在里面返回
  15. mutex->muxCount++; //互斥锁计数器加1
  16. mutex->owner = (VOID *)runTask; //当前任务拿到锁
  17. LOS_ListTailInsert(&runTask->lockList &mutex->holdList);//持有锁的任务改变了,节点挂到当前task的锁链表
  18. if ((runTask->priority > mutex->attr.prioceiling) && (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT)) {//看保护协议的做法是怎样的?
  19. LOS_BitmapSet(&runTask->priBitMap runTask->priority);//1.priBitMap是记录任务优先级变化的位图,这里把任务当前的优先级记录在priBitMap
  20. OsTaskPriModify(runTask mutex->attr.prioceiling);//2.把高优先级的mutex->attr.prioceiling设为当前任务的优先级.
  21. }//注意任务优先级有32个, 是0最高,31最低!!!这里等于提高了任务的优先级,目的是让其在下次调度中继续提高被选中的概率,从而快速的释放锁.
  22. return LOS_OK;
  23. }
  24. //递归锁muxCount>0 如果是递归锁就要处理两种情况 1.runtask持有锁 2.锁被别的任务拿走了
  25. if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {//第一种情况 runtask是锁持有方
  26. mutex->muxCount++; //递归锁计数器加1,递归锁的目的是防止死锁,鸿蒙默认用的就是递归锁(LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE)
  27. return LOS_OK; //成功退出
  28. }
  29. //到了这里说明锁在别的任务那里,当前任务只能被阻塞了.
  30. if (!timeout) {//参数timeout表示等待多久再来拿锁
  31. return LOS_EINVAL;//timeout = 0表示不等了,没拿到锁就返回不纠结,返回错误.见于LOS_MuxTrylock
  32. }
  33. //自己要被阻塞,只能申请调度,让出CPU core 让别的任务上
  34. if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁)
  35. return LOS_EDEADLK;//返回错误,自旋锁被别的CPU core 持有
  36. }
  37. OsMuxBitmapSet(mutex runTask (LosTaskCB *)mutex->owner);//设置锁位图,尽可能的提高锁持有任务的优先级
  38. owner = (LosTaskCB *)mutex->owner; //记录持有锁的任务
  39. runTask->taskMux = (VOID *)mutex; //记下当前任务在等待这把锁
  40. node = OsMuxPendFindPos(runTask mutex);//在等锁链表中找到一个优先级比当前任务更低的任务
  41. ret = OsTaskWait(node timeout TRUE);//task陷入等待状态 TRUE代表需要调度
  42. if (ret == LOS_ERRNO_TSK_TIMEOUT) {//这行代码虽和OsTaskWait挨在一起,但要过很久才会执行到,因为在OsTaskWait中CPU切换了任务上下文
  43. runTask->taskMux = NULL;// 所以重新回到这里时可能已经超时了
  44. ret = LOS_ETIMEDOUT;//返回超时
  45. }
  46. if (timeout != LOS_WAIT_FOREVER) {//不是永远等待的情况
  47. OsMuxBitmapRestore(mutex runTask owner);//恢复锁的位图
  48. }
  49. return ret;
  50. }

释放锁的主体函数 OsMuxPostOp

  1. //是否有其他任务持有互斥锁而处于阻塞状,如果是就要唤醒它,注意唤醒一个任务的操作是由别的任务完成的
  2. //OsMuxPostOp只由OsMuxUnlockUnsafe,参数任务归还锁了,自然就会遇到锁要给谁用的问题, 因为很多任务在申请锁,由OsMuxPostOp来回答这个问题
  3. STATIC UINT32 OsMuxPostOp(LosTaskCB *taskCB LosMux *mutex BOOL *needSched)
  4. {
  5. LosTaskCB *resumedTask = NULL;
  6. if (LOS_ListEmpty(&mutex->muxList)) {//如果互斥锁列表为空
  7. LOS_ListDelete(&mutex->holdList);//把持有互斥锁的节点摘掉
  8. mutex->owner = NULL;
  9. return LOS_OK;
  10. }
  11. resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(mutex->muxList)));//拿到等待互斥锁链表的第一个任务实体,接下来要唤醒任务
  12. if (mutex->attr.protocol == LOS_MUX_PRIO_INHERIT) {//互斥锁属性协议是继承会怎么操作?
  13. if (resumedTask->priority > taskCB->priority) {//拿到锁的任务优先级低于参数任务优先级
  14. if (LOS_HighBitGet(taskCB->priBitMap) != resumedTask->priority) {//参数任务bitmap中最低的优先级不等于等待锁的任务优先级
  15. LOS_BitmapClr(&taskCB->priBitMap resumedTask->priority);//把等待任务锁的任务的优先级记录在参数任务的bitmap中
  16. }
  17. } else if (taskCB->priBitMap != 0) {//如果bitmap不等于0说明参数任务至少有任务调度的优先级
  18. OsMuxPostOpSub(taskCB mutex);//
  19. }
  20. }
  21. mutex->muxCount = 1;//互斥锁数量为1
  22. mutex->owner = (VOID *)resumedTask;//互斥锁的持有人换了
  23. resumedTask->taskMux = NULL;//resumedTask不再等锁了
  24. LOS_ListDelete(&mutex->holdList);//自然要从等锁链表中把自己摘出去
  25. LOS_ListTailInsert(&resumedTask->lockList &mutex->holdList);//把锁挂到恢复任务的锁链表上,lockList是任务持有的所有锁记录
  26. OsTaskWake(resumedTask);//resumedTask有了锁就唤醒它,因为当初在没有拿到锁时处于了pend状态
  27. if (needSched != NULL) {//如果不为空
  28. *needSched = TRUE;//就走起再次调度流程
  29. }
  30. return LOS_OK;
  31. }

编程实例

本实例实现如下流程。

  • 任务Example_TaskEntry创建一个互斥锁,锁任务调度,创建两个任务Example_MutexTask1、Example_MutexTask2。Example_MutexTask2优先级高于Example_MutexTask1,解锁任务调度,然后Example_TaskEntry任务休眠300Tick。
  • Example_MutexTask2被调度,以永久阻塞模式申请互斥锁,并成功获取到该互斥锁,然后任务休眠100Tick,Example_MutexTask2挂起,Example_MutexTask1被唤醒。
  • Example_MutexTask1以定时阻塞模式申请互斥锁,等待时间为10Tick,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。10Tick超时时间到达后,Example_MutexTask1被唤醒,以永久阻塞模式申请互斥锁,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。
  • 100Tick休眠时间到达后,Example_MutexTask2被唤醒, 释放互斥锁,唤醒Example_MutexTask1。Example_MutexTask1成功获取到互斥锁后,释放锁。
  • 300Tick休眠时间到达后,任务Example_TaskEntry被调度运行,删除互斥锁,删除两个任务。
  1. /* 互斥锁句柄id */
  2. UINT32 g_testMux;
  3. /* 任务ID */
  4. UINT32 g_testTaskId01;
  5. UINT32 g_testTaskId02;
  6. VOID Example_MutexTask1(VOID)
  7. {
  8. UINT32 ret;
  9. printf("task1 try to get mutex, wait 10 ticks.\n");
  10. /* 申请互斥锁 */
  11. ret = LOS_MuxPend(g_testMux 10);
  12. if (ret == LOS_OK) {
  13. printf("task1 get mutex g_testMux.\n");
  14. /* 释放互斥锁 */
  15. LOS_MuxPost(g_testMux);
  16. return;
  17. } else if (ret == LOS_ERRNO_MUX_TIMEOUT ) {
  18. printf("task1 timeout and try to get mutex, wait forever.\n");
  19. /* 申请互斥锁 */
  20. ret = LOS_MuxPend(g_testMux LOS_WAIT_FOREVER);
  21. if (ret == LOS_OK) {
  22. printf("task1 wait forever, get mutex g_testMux.\n");
  23. /* 释放互斥锁 */
  24. LOS_MuxPost(g_testMux);
  25. return;
  26. }
  27. }
  28. return;
  29. }
  30. VOID Example_MutexTask2(VOID)
  31. {
  32. printf("task2 try to get mutex, wait forever.\n");
  33. /* 申请互斥锁 */
  34. (VOID)LOS_MuxPend(g_testMux LOS_WAIT_FOREVER);
  35. printf("task2 get mutex g_testMux and suspend 100 ticks.\n");
  36. /* 任务休眠100Ticks */
  37. LOS_TaskDelay(100);
  38. printf("task2 resumed and post the g_testMux\n");
  39. /* 释放互斥锁 */
  40. LOS_MuxPost(g_testMux);
  41. return;
  42. }
  43. UINT32 Example_TaskEntry(VOID)
  44. {
  45. UINT32 ret;
  46. TSK_INIT_PARAM_S task1;
  47. TSK_INIT_PARAM_S task2;
  48. /* 创建互斥锁 */
  49. LOS_MuxCreate(&g_testMux);
  50. /* 锁任务调度 */
  51. LOS_TaskLock();
  52. /* 创建任务1 */
  53. memset(&task1 0 sizeof(TSK_INIT_PARAM_S));
  54. task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask1;
  55. task1.pcName = "MutexTsk1";
  56. task1.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
  57. task1.usTaskPrio = 5;
  58. ret = LOS_TaskCreate(&g_testTaskId01 &task1);
  59. if (ret != LOS_OK) {
  60. printf("task1 create failed.\n");
  61. return LOS_NOK;
  62. }
  63. /* 创建任务2 */
  64. memset(&task2 0 sizeof(TSK_INIT_PARAM_S));
  65. task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask2;
  66. task2.pcName = "MutexTsk2";
  67. task2.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
  68. task2.usTaskPrio = 4;
  69. ret = LOS_TaskCreate(&g_testTaskId02 &task2);
  70. if (ret != LOS_OK) {
  71. printf("task2 create failed.\n");
  72. return LOS_NOK;
  73. }
  74. /* 解锁任务调度 */
  75. LOS_TaskUnlock();
  76. /* 休眠300Ticks */
  77. LOS_TaskDelay(300);
  78. /* 删除互斥锁 */
  79. LOS_MuxDelete(g_testMux);
  80. /* 删除任务1 */
  81. ret = LOS_TaskDelete(g_testTaskId01);
  82. if (ret != LOS_OK) {
  83. printf("task1 delete failed .\n");
  84. return LOS_NOK;
  85. }
  86. /* 删除任务2 */
  87. ret = LOS_TaskDelete(g_testTaskId02);
  88. if (ret != LOS_OK) {
  89. printf("task2 delete failed .\n");
  90. return LOS_NOK;
  91. }
  92. return LOS_OK;
  93. }

结果验证

  1. task2 try to get mutex wait forever.
  2. task2 get mutex g_testMux and suspend 100 ticks.
  3. task1 try to get mutex wait 10 ticks.
  4. task1 timeout and try to get mutex wait forever.
  5. task2 resumed and post the g_testMux
  6. task1 wait foreverget mutex g_testMux.

总结

1.互斥锁解决的是任务间竞争共享内存的问题.

2.申请锁失败的任务会进入睡眠OsTaskWait,内核会比较持有锁的任务和申请锁任务的优先级,把持有锁的任务优先级调到尽可能的高,以便更快的被调度执行,早日释放锁.

3.释放锁的任务会在等锁链表中找一个高优先级任务,通过OsTaskWake唤醒它,并向调度算法申请调度.但要注意,调度算法只是按优先级来调度,并不保证调度后的任务一定是要唤醒的任务.

4.互斥锁篇关键是看懂 OsMuxPendOp 和 OsMuxPostOp 两个函数.

鸿蒙内核源码分析.总目录

v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o

百万汉字注解.百篇博客分析

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee| github| csdn| coding >

百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto| csdn| harmony| osc >

关注不迷路.代码即人生

QQ群:790015635 | 入群密码: 666

原创不易,欢迎转载,但请注明出处.

鸿蒙内核源码分析(互斥锁篇) | 比自旋锁丰满的互斥锁 | 百篇博客分析OpenHarmony源码 | v27.02的更多相关文章

  1. 鸿蒙内核源码分析(自旋锁篇) | 当立贞节牌坊的好同志 | 百篇博客分析OpenHarmony源码 | v26.02

    百篇博客系列篇.本篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 当立贞节牌坊的好同志 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 当立贞节牌坊 ...

  2. v79.01 鸿蒙内核源码分析(用户态锁篇) | 如何使用快锁Futex(上) | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(用户态锁篇) | 如何使用快锁Futex(上) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...

  3. v80.01 鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下) | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(内核态锁篇) | 如何实现快锁Futex(下) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...

  4. 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 | 百篇博客分析OpenHarmony源码 | v71.01

    子曰:"我非生而知之者,好古,敏以求之者也." <论语>:述而篇 百篇博客系列篇.本篇为: v71.xx 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 ...

  5. 鸿蒙内核源码分析(文件句柄篇) | 深挖应用操作文件的细节 | 百篇博客分析OpenHarmony源码 | v69.01

    百篇博客系列篇.本篇为: v69.xx 鸿蒙内核源码分析(文件句柄篇) | 深挖应用操作文件的细节 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说 ...

  6. 鸿蒙内核源码分析(VFS篇) | 文件系统和谐共处的基础 | 百篇博客分析OpenHarmony源码 | v68.01

    子曰:"质胜文则野,文胜质则史.文质彬彬,然后君子." <论语>:雍也篇 百篇博客系列篇.本篇为: v68.xx 鸿蒙内核源码分析(VFS篇) | 文件系统和谐共处的基 ...

  7. 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 百篇博客分析OpenHarmony源码 | v33.02

    百篇博客系列篇.本篇为: v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁 ...

  8. 鸿蒙内核源码分析(事件控制篇) | 任务间多对多的同步方案 | 百篇博客分析OpenHarmony源码 | v30.02

    百篇博客系列篇.本篇为: v30.xx 鸿蒙内核源码分析(事件控制篇) | 任务间多对多的同步方案 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当 ...

  9. 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 百篇博客分析OpenHarmony源码 | v29.01

    百篇博客系列篇.本篇为: v29.xx 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当立 ...

随机推荐

  1. 从元素抽取属性,文本和HTML

    问题 在解析获得一个Document实例对象,并查找到一些元素之后,你希望取得在这些元素中的数据. 方法 要取得一个属性的值,可以使用Node.attr(String key) 方法 对于一个元素中的 ...

  2. C++程序调试方式总结

    bug调试要根据应用场景和条件,选择什么样子的调试方式很大程度上不是你想选择什么样的调试方式,而是还剩下什么样子的调试方式可用.下面就根据不同的场景和条件来总结一下. 目录: 1.gdb调试或者IDE ...

  3. 使用dom4j工具:获得文本内容(四)

    package dom4j_read; import java.io.File; import org.dom4j.Document; import org.dom4j.Element; import ...

  4. Web安全-信息收集

    信息收集 前言:在渗透测试过程中,信息收集是非常重要的一个环节,此环节的信息将影响到后续成功几率,掌握信息的多少将决定发现漏洞的机会的大小,换言之决定着是否能完成目标的测试任务.也就是说:渗透测试的思 ...

  5. Buffer和Cache的异同

    Buffer的本质是缓冲,常见实例如下面这个: 对,就是铁道端头那个巨大的弹簧一类的东西.作用是万一车没停住(是没停住啊,刹车了但是差一点没刹住那种,不是不拉刹直接撞上来),撞弹簧上减速降低危险,起到 ...

  6. js获取文件名和后缀名

  7. go语言 切片表达式

    切片表达式 切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片. 切片表达式中的low和high表示一个索引范围(左包含,右不包含),得到的切片长度=high-low,容量等于得到的切 ...

  8. Nginx 极简入门教程!(转)

    基本介绍 Nginx 是一个高性能的 HTTP 和反向代理 web 服务器,同时也提供了 IMAP/POP3/SMTP 服务. Nginx 是由伊戈尔·赛索耶夫为俄罗斯访问量第二的 Rambler.r ...

  9. 记录Mac下使用Charles抓包

    抓包 简述 在网络应用如后端系统,app,小程序等的开发过程中,免不了接口可能会报错,但是一般在app中或者小程序中没有便捷的console控制台,而且线上环境也不会开启调试模式,所以想看一下接口的响 ...

  10. client-go实战之五:DiscoveryClient

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...