深入浅出进程控制

news2024/10/5 19:11:27

image-20230120134720847

文章目录

  • 进程控制
    • 浅谈fork
      • 写时拷贝
      • fork调用失败的原因
    • 进程终止
      • 进程退出的场景
      • 进程常见退出方法
        • 查看进程退出码
          • echo $? :查看进程退出码
          • exit和_exit
    • 进程等待
      • 进程等待的方法
        • wait
        • waitpid
      • 获取子进程status
        • 宏定义查看进程是否正常退出,查看退出码
        • 再谈僵尸进程
      • 浅谈阻塞等待和非阻塞等待
        • 宏定义WNOHANG:非阻塞等待
    • 进程程序替换
      • 替换函数
        • execl
        • execlp
        • execv
        • execvp
        • execle
        • putenv
        • 先加载还是先调用函数?

进程控制

浅谈fork

fork函数可以从一个已存在的进程创建出一个新的进程。新进程为子进程,而原进程为父进程。

#include<unistd.h>
pid_t fork(void);//pid_t为返回值
返回值:fork成功就把子进程pid返回给父进程,而把0返回给子进程,如果fork失败就把-1返回给父进程,子进程没有返回值

进程调用fork,

内核首先分配新的内存块和内核数据结构给子进程,将父进程部分数据结构拷贝给子进程,然后添加子进程到系统进程列表中,fork返回,开始调度器调度。

image-20230116224347288

fork之后,父子进程谁先执行完全由调度器决定。

写时拷贝

通常,父子进程代码共享,物理空间也是使用同一块,但任何一个进程尝试写入,操作系统先进行进程数据拷贝,让不同的进程数据进行分离,更改页表映射,然后再让进程进行修改—写实拷贝。

image-20230116225424560

fork调用失败的原因

原因:系统中有太多的进程;实际用户的进程数超过了限制呀

这里有一段代码,运行后可以看到自己的操作系统最多可以容纳多少进程数。如果有虚拟机或者有云服务器的小伙伴可以试试噢,系统崩溃后退出系统等一会重启就好

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6   int num=0;
  7   while(1)          
  8   {
  9     int ret=fork();
 10     if(ret<0)//如果创建子进程失败
 11     {         
 12       printf("fork error!,%d \n",num);
 13       break;
 14     }                
 15     else if(ret==0)                
 16     {  
 17       //子进程                          
 18       while(1)
 19       sleep(1);
 20 
 21     }
 22     //父进程
 23     num++;
 24   }
 25   return 0;
 26 }  

进程终止

咱们写c++或者c代码时,大多数从main函数开始写写写,然后写完了就return 0;那么这个return 0有啥意义呢?所谓的return 0这个0就是进程退出码,退出码记录着进程退出的结果等等

进程退出的场景

进程退出的场景无非就三种

代码运行完毕,结果正确

代码运行完毕,结果不正确

代码运行异常终止了

那么当代码运行完毕后,结果在哪可以看到呢?

进程常见退出方法

查看进程退出码

echo $? :查看进程退出码

首先我写了这段代码,如果num等于5050那么main函数的进程退出码就是1,否则是0

image-20230116234115423

然后运行后,第一次echo $?查看到就是mytest.c的main函数进程的退出码1.而echo $?也是进程,退出码是0,那么为什么后者进程的退出码是0呢?

image-20230116234556398

return 0这个0标识着代码跑完了,进程执行的结果正确,而非0标识代码跑完了,结果不正确!

而!0里面不同的数字标识着不同的错误

这里要提到一个函数 strerror,该函数可以把进程退出码转化成相应的可以概括结果的字符串;然后这里我写一个小程序打印200以内的进程退出码对应的结果的字符串概括

image-20230116235737464

我把结果拷贝到下面了,可以看到Linux系统下一共有134种进程退出码,每一个退出码都有对应的结果。其中第一种0代表着进程执行的结果正确,而其他都是进程执行的结果错误对应的原因。

0: Success
 1: Operation not permitted
 2: No such file or directory
 3: No such process
 4: Interrupted system call
 5: Input/output error
 6: No such device or address
 7: Argument list too long
 8: Exec format error
 9: Bad file descriptor
 10: No child processes
 11: Resource temporarily unavailable
 12: Cannot allocate memory
 13: Permission denied
 14: Bad address
 15: Block device required
 16: Device or resource busy
 17: File exists
 18: Invalid cross-device link
 19: No such device
 20: Not a directory
 21: Is a directory
 22: Invalid argument
 23: Too many open files in system
 24: Too many open files
 25: Inappropriate ioctl for device
 26: Text file busy
 27: File too large
 28: No space left on device
 29: Illegal seek
 30: Read-only file system
 31: Too many links
 32: Broken pipe
 33: Numerical argument out of domain
 34: Numerical result out of range
 35: Resource deadlock avoided
 36: File name too long
 37: No locks available
 38: Function not implemented
 39: Directory not empty
 40: Too many levels of symbolic links
 41: Unknown error 41
 42: No message of desired type
 43: Identifier removed
 44: Channel number out of range
 45: Level 2 not synchronized
 46: Level 3 halted
 47: Level 3 reset
 48: Link number out of range
 49: Protocol driver not attached
 50: No CSI structure available
 51: Level 2 halted
 52: Invalid exchange
 53: Invalid request descriptor
 54: Exchange full
 55: No anode
 56: Invalid request code
 57: Invalid slot
 58: Unknown error 58
 59: Bad font file format
 60: Device not a stream
 61: No data available
 62: Timer expired
 63: Out of streams resources
 64: Machine is not on the network
 65: Package not installed
 66: Object is remote
 67: Link has been severed
 68: Advertise error
 69: Srmount error
 70: Communication error on send
 71: Protocol error
 72: Multihop attempted
 73: RFS specific error
 74: Bad message
 75: Value too large for defined data type
 76: Name not unique on network
 77: File descriptor in bad state
 78: Remote address changed
 79: Can not access a needed shared library
 80: Accessing a corrupted shared library
 81: .lib section in a.out corrupted
 82: Attempting to link in too many shared libraries
 83: Cannot exec a shared library directly
 84: Invalid or incomplete multibyte or wide character
 85: Interrupted system call should be restarted
 86: Streams pipe error
 87: Too many users
 88: Socket operation on non-socket
 89: Destination address required
 90: Message too long
 91: Protocol wrong type for socket
 92: Protocol not available
 93: Protocol not supported
 94: Socket type not supported
 95: Operation not supported
 96: Protocol family not supported
 97: Address family not supported by protocol
 98: Address already in use
 99: Cannot assign requested address
 100: Network is down
 101: Network is unreachable
 102: Network dropped connection on reset
 103: Software caused connection abort
 104: Connection reset by peer
 105: No buffer space available
 106: Transport endpoint is already connected
 107: Transport endpoint is not connected
 108: Cannot send after transport endpoint shutdown
 109: Too many references: cannot splice
 110: Connection timed out
 111: Connection refused
 112: Host is down
 113: No route to host
 114: Operation already in progress
 115: Operation now in progress
 116: Stale file handle
 117: Structure needs cleaning
 118: Not a XENIX named type file
 119: No XENIX semaphores available
 120: Is a named type file
 121: Remote I/O error
 122: Disk quota exceeded
 123: No medium found
 124: Wrong medium type
 125: Operation canceled
 126: Required key not available
 127: Key has expired
 128: Key has been revoked
 129: Key was rejected by service
 130: Owner died
 131: State not recoverable
 132: Operation not possible due to RF-kill
 133: Memory page has hardware error
 134: Unknown error 134
 135: Unknown error 135
 136: Unknown error 136
 137: Unknown error 137

一般情况下,进程正常终止,要么是main函数返回(别的函数返回为函数调用结束);调用exit;系统调用_exit;

exit和_exit

exit:调用这个函数后可以让进程直接退出 ,参数为进程退出码

image-20230117102637450

然后我写了这么一个代码,如果main函数没退出则进入死循环。

image-20230117103056504

那么运行后查看看到main函数确实是退出了

image-20230117103227709

然后我再写这段代码,一般情况下其他函数结束为函数调用结束,而我在这个addtosum函数这里在返回值前面调用了个exit函数,看它是退出函数调用还是退出进程。

image-20230117104200445

事实证明exit函数在任意函数调用都是直接退出进程!

image-20230117104257884

exit是库函数,而_exit是系统调用,那么exit的底层实现也是 _exit,那么exit和 _exit有什么区别呢?

写了这段代码

image-20230117105010830

运行后可以看到两秒后hello bug打印出来了

image-20230117105101479

那么把exit改成_exit呢?

image-20230117105147945

可以看到压根就没打印出来

image-20230117105209120

这个对比可以知道,库函数的exit和系统调用的_eixt的区别是:exit在退出进程前会刷新缓冲区,而 _exit则不会。并且可以推断出缓冲区不在操作系统中,应该在用户空间里。

image-20230117111351231

进程等待

如果子进程退出,父进程对该进程不管不顾,那么就可能造成僵尸进程问题,进而导致内存泄露。另外进程一旦变成了僵尸进程,就连杀死进程的指令kill -9都无能为力。那么父进程应该怎么管理退出的子进程呢?

子进程运行完成后,父进程要通过进程等待的方式:回收子进程资源,获取子进程退出信息,这样过后就避免了僵尸进程的出现。

进程等待的方法

wait

父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

第一个函数的参数为*status,是一个整数类型的指针,大多数情况都传NULL,如果成功就返回被收集子进程的pid,如果失败就返回-1

image-20230117113954135

然后我写了这样的函数,创建了一个子进程,进到子进程里面打印子进程pid和父进程ppid,然后睡眠一秒,五秒后子进程退出,但由于父进程也睡眠了,所以会进入僵尸状态,之后几秒后父进程回收子进程并打印出wait的返回值

image-20230117120944756

运行后看到确实如此

image-20230117121622756

waitpid

我写了这么一段代码,ret获取到子进程的pid,stastus获取到子进程的退出信息。

image-20230117154241534

那么status到底是啥呢?

image-20230117154410721

获取子进程status

1.wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充;如果传递NULL,表示不关心子进程的退出状态信息;否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

2.status不能简单的当作整形来看待,可以当作位图来看待,即status要都能表示进程退出时的三种场景。(代码运行完毕,结果正确;代码运行完毕,结果不正确;代码运行异常终止了)

整数status的二进制下有32个比特位,现在先看前16个比特位(0-15);

第8-15个比特位(次低八位)存放进程的退出状态即子进程的退出码(通过退出状态得知进程运行完结果是否正确)退出码为0则结果正确。非0则对应错误的情况

image-20230118215443955

第0-6位(低七位)存放进程的终止信号(通过终止信号得知进程是否正常退出),第8位存放core dump标志(如图:status的二进制结构),终止信号为0—进程正常退出。非0为异常,通过kill -l可以看到大部分终止信号的情况。

image-20230118211643871

通过status&0x7F[01111111]得到终止信号再通过(status>>8)&0xFF[011111111]得到退出码

image-20230118220654591

image-20230118220728630

相应的,通过kill杀死子进程也可以获取到相应的终止信号

比如我对子进程kill -3,那么子进程返回父进程的终止信号也是3

image-20230118223901470

宏定义查看进程是否正常退出,查看退出码

WIFEXITED(status) :若正常终止子进程返回的状态,则为真,(查看进程是否正常退出)

WEXITSTATUS(status) :若WIFEXITED非零,提取子进程退出码(查看进程的退出码)

  1#include<string.h>
  2 #include<stdio.h>
  3 #include<unistd.h>
  4 #include<assert.h>
  5 #include<sys/types.h>
  6 #include<sys/wait.h>
  7 #include<stdlib.h>
  8 int  main()
  9 {
 10  pid_t id=fork();
 11 assert(id!=-1);
 12 if(id==0)
 13{
 14 //子进程
 15 int num=30;
 16 while(num)
 17 {
 18   printf("child running,pid: %d ,ppid: %d ,num: %d\n",getpid(),getppid(),num--);
 19   sleep(1);
 20 }
 21 exit(10);
 22 }
 23 //父进程
 24   int status=0;
 25   int ret=waitpid(id,&status,0);
 26   if(ret>0)
 27 {                                                                                                 
 28   //判断子进程是否正常退出
 29   if(WIFEXITED(status))//子进程正常退出
 30   {//判断子进程运行的结果
 31     printf("exit code: %d\n",WEXITSTATUS(status));
 32   }else 
 33   {
 34     //子进程异常终止
 35     printf("child exit abnormally!\n ");
 36   }
 37 //  printf("wait success,exit code: %d, sig: %d\n",(status>>8)&0xFF,status&0x7F);
 38 }
 39 return 0;
 40 }

image-20230119103915799

再谈僵尸进程

当子进程退出时,会把代码和数据释放掉,但是退出信息(退出码和退出信号)要存在子进程的pcb中,此时子进程为Z状态,当父进程系统调用waitpid/wait时,父进程会通过子进程id从子进程pcb中拿退出信息到status里。

image-20230118222011229

浅谈阻塞等待和非阻塞等待

小帅—>父进程;女朋友—>子进程;打电话—>系统调用wait/waitpid

这个男人叫小帅,他有个女朋友,一天小帅约女朋友去看电影,约好了9点到女朋友楼下等她。小帅如期而至。但是没有看到女朋友身影,小帅打电话给女朋友,女朋友说在化妆要等一会,小帅说好阿,但是不要挂电话,小帅等阿等,过两分钟问女朋友一次化好了没,过两分钟又问,直到女朋友到了楼下才挂电话。

不挂电话检测女朋友状态就是阻塞等待。

又一天,小帅约女朋友去吃饭,也是9点到楼下。小帅如期而至,打电话给女朋友,女朋友说在化妆要等一下,这次小帅说挂电话,等等在打电话给她。小帅在等等的时候一会看下球赛,一会看下书,过了一会打电话给女朋友问好了没,女朋友说还没好再等等。小帅就挂了电话继续等,等的时候做别的事情。

打电话—状态检测,如果没有就绪,立即返回(挂电话);每一次都是非阻塞等待。多次非阻塞等待—轮询。

宏定义WNOHANG:非阻塞等待

把宏定义的WHOHANG传给waitpid,则为非阻塞等待。

image-20230119114026860

WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID,如果waitpid调用失败,则返回-1

#include<string.h>
  2 #include<stdio.h>
  3 #include<unistd.h>
  4 #include<assert.h>
  5 #include<sys/types.h>
  6 #include<sys/wait.h>
  7 #include<stdlib.h>
  8 int  main()
  9 {
 10  pid_t id=fork();
 11 assert(id!=-1);
 12 if(id==0)
 13 {
 14 //子进程
 15 int num=3;                                                                                        
 16 while(num)
 17 {
 18   printf("child running,pid: %d ,ppid: %d ,num: %d\n",getpid(),getppid(),num--);
 19   sleep(3);
 20 }
 21 exit(10);
 22 }
 23 //父进程
 24 int status=0;
 25 while(1)
 26 {
 27   pid_t ret=waitpid(id,&status,WNOHANG);//WHOHANG:非阻塞->子进程没有退出,父进程检测时候,立即返回
 28   if(ret==0)
 29   {
 30     //waitpid调用成功,子进程没有退出
 31   printf("wait done,but child is running...\n");
 32   }
  33   else if(ret>0)
 34   {
 35     //waitpid调用成功,子进程退出了
 36     printf("wait success,exit code: %d,sig: %d\n",(status>>8)&0xFF,status&0x7F);
 37     break;
 38   }
 39   else{
 40     //waitpid调用失败
 41     printf("waitpid call failed!\n");
 42     break;
 43   }
 44   sleep(1);
 45 }
 46 return 0;}

下面图是父进程非阻塞等待且轮询等待,最后子进程正常退出。

image-20230119112719606

这里我传一个错误的id给父进程,即waitpid调用失败

image-20230119113329498

可以看到打印了调用失败的情况,且父进程退出而子进程还在运行被OS领养

image-20230119113524542

非阻塞等待的意义:不会占用父进程的所有资源,可以在轮询期间做别的事情。

在父进程非阻塞等待子进程期间,可以做其他事,我这里写了几个task,选择让父进程回调函数的方式执行。

  1 #include<string.h>
  2 #include<stdio.h>
  3 #include<unistd.h>
  4 #include<assert.h>
  5 #include<sys/types.h>
  6 #include<sys/wait.h>
  7 #include<stdlib.h>
  8 
  9 #define NUM 10
 10 typedef void (*func_t)();//函数指针-void 是函数返回类型,fun_t是函数名,没有参数
 11 
 12 func_t handerTask[NUM];
 13 
 14 void task1()
 15 {
 16   printf("hander task1\n");
 17 }
 18 void task2()
 19 {
 20   printf("hander task2\n");
 21 }
 22 void task3()
 23 {
 24   printf("hander task3\n");
 25 }
 26 void loadTask()
 27 {
 28   memset(handerTask,0,sizeof(handerTask));
 29   handerTask[0]=task1;
 30   handerTask[1]=task2;
 31   handerTask[2]=task3;
 32 }
 33 int main()
 34 {
 35  pid_t id=fork();
 36 assert(id!=-1);
 37 if(id==0)  
 38 {
 39 //子进程
 40 int num=3;
 41 while(num)
 42 {
 43   printf("child running,pid: %d ,ppid: %d ,num: %d\n",getpid(),getppid(),num--);
 44   sleep(3);
 45 }
 46 exit(10);
 47 }
 48 //父进程
 49 loadTask();
 50 int status=0;
 51 while(1)
 52 {
 53   pid_t ret=waitpid(id,&status,WNOHANG);//WHOHANG:非阻塞->子进程没有退出,父进程检测时候,立即返回
 54   if(ret==0)
 55   {
 56     //waitpid调用成功,子进程没有退出
 57   printf("wait done,but child is running...\n");
 58  for(int i=0;handerTask[i]!=NULL;i++)
 59  {
 60    handerTask[i]();//采用回调的方式,执行我们想让父进程在非阻塞等待时做的事情。
 61  }
 62 
 63 
 64 
 65   }
 66   else if(ret>0)
 67   {
 68     //waitpid调用成功,子进程退出了
 69     printf("wait success,exit code: %d,sig: %d\n",(status>>8)&0xFF,status&0x7F);
 70     break;
 71   }                                                                                                                                                                                                         
 72   else{
 73     //waitpid调用失败
 74     printf("waitpid call failed!\n");
 75     break;
 76   }
 77   sleep(1);
 78 }
 79 return 0;}

image-20230119172117690

进程程序替换

程序替换的本质:将指定程序的代码和数据加载到指定的位置上,且覆盖之前的代码和数据。

进程通过pcb找到自己的虚拟空间,再通过页表找到物理空间执行自己的代码和数据,而程序替换通过exec函数把磁盘上的要替换的代码和数据加载到当前进程的物理内存中并覆盖,那么进程执行的就是后来的代码和数据拉!

此时只是替换代码和数据,没有创建新的进程。

image-20230119191354200

替换函数

这里有exec开头的函数,统称exec函数

函数调用成功—程序替换,调用失败—不替换

execl

image-20230119185538426

带l字符的函数—传路径,像list一样用next串起来

要执行哪一个程序:传程序的相对路径/绝对路径

要怎么执行:跟命令行输入一样:“程序名”,“选项1”,“选项2”…,NULL【exec函数都要以NULL结尾】

可变参数列表:给函数传递不同个数的参数

image-20230119190040684

image-20230119192401749

execl函数如果调用失败了就返回-1,如果成功了就不返回,即便成功了原先后面的代码都会被替换掉,所以返回了也没有用处。

image-20230119193050052

Q:子进程在调用exec函数进行程序替换时,会影响父进程吗?

A:此时父进程和子进程共享同一块代码和数据,当子进程调用exec函数时,操作系统会给子进程进行写时拷贝,然后再进行程序替换。父子进程互不影响,这也体现了进程的独立性。

这样体现了创建子进程的目的:

1.让子进程执行父进程的一部分。

2.让子进程进行程序替换,执行一个全新的程序。

execlp

带p字符的函数,都只需传程序名即可

image-20230119200503232

image-20230119200724918

一样能跑起来

image-20230119200640618

那么如果execl和execlp函数放在同一个函数,两者重复吗?

前者是通过路径传参,后者是通过环境变量PATH寻找函数名,不重复。

execv

image-20230119201421265

image-20230119202003362

image-20230119201947564

execvp

image-20230119202134707

image-20230119202341403

那如何让exec函数调用自己写的程序呢?

这里我写了一些标识性代码

image-20230119202912585

通过创建伪目标all,来达到同时执行多个目标文件。

image-20230119203345913

现在我想要myexec去调用我写的mycom程序

  1 #include<stdio.h> 
  2 #include<unistd.h>
  3 #include<stdlib.h>  
  4 #include<assert.h>   
  5 #include<sys/wait.h>
  6 #include<sys/types.h>
  7 int main()
  8 {                                   
  9                 
 10   printf("process is running...\n");
 11 pid_t id=fork();
 12 assert(id!=-1);
 13 if(id==0) 
 14 {        
 15   //子进程  
 16 sleep(1);                       
 17 //  char*const argv_[]={"ls","-a","-l","--color=auto",NULL};
 18   execl("./mycom","mycom",NULL);
 19 exit(1);             
 20   //execvp("ls",argv_);            
 21   //execv("/usr/bin/ls",argv_);
 22   //  execl("/user/bin/ls/"/*传程序路径*/,"ls","-a","-l","--color=auto",NULL/*想怎么执行*/);
 23 //  execlp("ls"/*传程序名*/,"ls","-a","-l","--color=auto",NULL/*想怎么执行*/);
 24 //  //全部的exec函数参数都是以NULL结尾
 25              
 26 }                               
 27 int status=0;
 28 pid_t ret=waitpid(id,&status,0);
 29 if(id>0)                                       
 30 {
 31   printf("wait success:%d ,sig number: %d,child exit code:%d\n",ret,status&0x7F,(status>>8)&0xFF);
 32 }          
 33   printf("process running done...\n");
 34   return 0;
 35 
 36 }

image-20230119235145750

image-20230119234945477

且可以使用程序替换,调用任何后端语言对应的可执行程序。

execle

image-20230120120212989

在mycom.c文件调用PATH,PWD环境变量,和一个MYENV的自定义变量

image-20230120121310297

然后myexec.c里的execle函数调用自定义变量

image-20230120121452370

可以看到环境变量没调用到但是自定义变量调用到了

image-20230120121555488

这次execle函数调用环境变量

image-20230120121723771

可以看到环境变量被调用了,但是自定义变量没有被调用

image-20230120121821222

那么即想调用自定义变量也想调用环境变量呢?

putenv

putenv:把自定义变量导入环境变量的表里

image-20230120122253387

把自定义变量MYENV加入到环境变量表里

image-20230120122734099

image-20230120122709082

先加载还是先调用函数?

main函数有命令行参数,参数有程序,环境变量等等,那么是先调用main函数还是先加载各种命令行参数到内存中呢?
先加载!因为main函数也要被传参!命令行参数和环境变量等等先加载到内存,如果函数需要就传参!

image-20230120130432363

实际上,上面提到的各种exec函数都是系统调用execve的封装,而各种封装后也是为了适用各种应用场景。

image-20230120131026386

好了,这里小结一下,重点讲解了进程终止:进程退出的三种情形,查看进程退出码,进程终止的方法;进程等待:进程等待的方法,如何获取子进程进程状态和退出码,使用宏定义查看进程是否正常退出;阻塞等待和非阻塞等待的介绍和区别;进程替换:五种进程替换函数的使用,putenv函数的使用等等。这篇写了好几天,制作不易,求点赞~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/173616.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于.Net Core开发的支付SDK,简化支付功能开发

更多开源项目请查看&#xff1a;一个专注推荐.Net开源项目的榜单 在我们做项目中&#xff0c;不管是电商系统、外卖系统、还是上门维修系统等等&#xff0c;都需要支付功能&#xff0c;这就需要我们与第三方支付平台进行对接&#xff0c;但是第三方平台文档&#xff0c;往往都存…

05语法分析——自下而上分析

文章目录一、自下而上分析基本问题二、算符优先分析构造FIRSTVT(P)的算法构造LASTVT(P)的算法构造优先表的算法三、LR分析法1.LR(0)构造LR(0)项目集规范族构造识别活前缀的DFA构造LR(0)分析表2.SLRSLR解决冲突办法SLR(1)分析表的构造算法3.LR(1)【规范LR】LR(1)项目集I的闭包状…

【MySQL基础】运算符及相关函数详解

序号系列文章3【MySQL基础】MySQL基本数据类型4【MySQL基础】MySQL表的七大约束5【MySQL基础】字符集与校对集详解6【MySQL基础】MySQL单表操作详解文章目录前言MySQL运算符1&#xff0c;算术运算符1.1&#xff0c;算术运算符的基本使用1.2&#xff0c;常用数学函数的基本使用2…

iOS:OpenGLES 实验室之2D篇 第一弹 の 智能弹幕

本文字数&#xff1a;3046字预计阅读时间&#xff1a;15 分钟iOS&#xff1a;OpenGLES 实验室之2D篇 第一弹 の 智能弹幕笔者之前发表的音视频文章&#xff0c;有图像的处理&#xff0c;音频的重采样等等&#xff0c;都属于入门级别。通过阅读它们&#xff0c;读者能对音视频有…

【复习 自用】JavaScript知识汇总(DOM)

注&#xff1a;之前学过JavaScript&#xff0c;本贴仅用于复习(自用)&#xff0c;建议没基础的朋友先学基础。会混入typescript&#xff01; 更新中~~~~~ Dom核心内容 创建节点 ① document.write() 是直接将内容写入页面的内容流&#xff0c;但是文档流执行完毕&#xff0c…

云原生技能树-docker caontainer 操作

运行 一个Docker镜像(image)运行后&#xff0c;就是一个容器实例&#xff0c;称为container 以镜像hello-world为例&#xff0c;启动容器&#xff1a; docker container run -it hello-world 可以看到输出了Hello World 信息&#xff1a; 以下描述错误的是&#xff1f; 答…

数字逻辑理论——从卡诺图到门电路

卡诺图化简 卡诺图化简 第一步&#xff1a;在卡诺图中圈出相邻为1的小方格&#xff08;方格的个数为2m2^{m}2m&#xff09;&#xff0c;圈里面的1越多越好&#xff0c;并且这个小方格可以重复使用。 第二步&#xff1a;上一步中的方格或者圈出来的方框——每一个都代表一个与…

Linux软件安装及管理程序

Linux安装及管理程序Linux软件安装及管理程序一、Linux应用程序基础二、RPM软件包管理工具2.1、RPM介绍2.2、RPM命令三、源码编译安装四、yum安装Linux软件安装及管理程序 一、Linux应用程序基础 应用程序与系统命令的关系 角色系统命令应用程序文件位置般在/bin和/sbin目录…

linux系统中实现智能家居的基本方法

大家好&#xff0c;今天主要和大家分享一下&#xff0c;智能家居物联网的基本实现与操作方法。 目录 第一&#xff1a;智能家居基本简介 第二&#xff1a;测试WIFI模块功能 第三&#xff1a;智能家居物联UI界面开发 第四&#xff1a;核心代码的具体实现 第五&#xff1a;最…

【阅读笔记】《重构》 第三四章

第三章 代码的味道 DuplicatedCode(重复代码) 同一个类的两个函数含有相同的表达式两个互为兄弟的子类含有相同表达式两个毫不相干的类出现重复代码 LongMethod(过长函数) 函数不宜过长&#xff0c;函数越长越难理解如果想利用单个类做太多事情&#xff0c;其内往往就会出现…

联合证券|港股再融资“春江水暖” 资本争购热门赛道企业

进入2023年&#xff0c;港股再融资商场有所回暖。到1月18日&#xff0c;已有27家港股上市公司发布拟配售股份&#xff08;简称“配股”&#xff09;再融资&#xff0c;募资总额164.01亿港元&#xff0c;较上一年同期增加148.16%。其间&#xff0c;微盟集团的配股再融资吸引了众…

fpga实操训练(lcd测试)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 前面我们测试过vga输出,当时是找了一个老式的显示器来完成的,也就是本身自带vga接口的显示器。但是,现在市面上大部分显示器都是默认支持hdmi接口的。所以说,如果真的想用fpga测…

都说InnoDB好,那还要不要使用Memory引擎?

我在上一篇文章末尾留给你的问题是:两个 group by 语句都用了 order by null,为什么使用内存临时表得到的语句结果里,0 这个值在最后一行;而使用磁盘临时表得到的结果里,0 这个值在第一行? 今天我们就来看看,出现这个问题的原因吧。 内存表的数据组织结构 为了便于分…

如何在SpringBoot项目中访问静态资源

在springboot项目中如果要在不集成templates的情况下访问静态资源需要做以下配置 1.在项目的application.yml文件中做如下配置 spring:profiles:active: devmvc:view:prefix: /suffix: .html 重点在 配置后生成为WebMvcProperties 配置类。该配置类中有一个内部类View Conf…

各式各样图标的特点

笔者近期在翻看各种样式的图标&#xff0c;逛了下 Iconfont、IconFinder 等图标网站&#xff0c;现根据自己的经验&#xff0c;总结了一些图标特点的描述用语 线性/面性/线面组合平面/立体无层次感&#xff08;或阴影&#xff09;/有层次感&#xff08;或阴影&#xff09;无填…

Oracle强制加了hint实效三种连接方式使用场合判断

开发写了一个语句使用了connect by level函数 SELECT DISTINCT CTMID FROM ( SELECT CTMID, REGEXP_SUBSTR(FLTUSERIDSTR, ‘[^;]’, 1, l) AS userid FROM s_userinfo,(SELECT LEVEL l FROM DUAL CONNECT BY LEVEL<300) b WHERE l < LENGTH(FLTUSERIDSTR) - LENGTH(rege…

链表的算法题

目录 题型一、克隆含有rand指针的链表 笔试&#xff1a;哈希表 面试&#xff1a;不用容器&#xff0c;模拟哈希表的功能 题型二、给一个单链表头节点Head&#xff0c;判断是否构成回文 题型三、将单链表按某值划分为左边小&#xff0c;中间相等&#xff0c;右边大 6个变量…

Allegro如何设置创建Pin Pair的快捷键操作指导

Allegro如何设置创建Pin Pair的快捷键操作指导 在做PCB设计的时候需要做一组信号的等长,需要使用到创建Pin Pair的功能,如下图,如果每个网络都右键去选择添加比较浪费时间,如下图 Allegro支持给创建一个Create Pin Pair的快捷键位 具体操作如下 打开规则管理器选择Tools

单调栈与单调队列

单调栈与单调队列一、单调栈1.1 思路1.2 例题&#xff1a;单调栈二、单调队列2.1 思路2.2 例题&#xff1a;滑动窗口一、单调栈 1.1 思路 单调栈主要解决以下问题&#xff1a; 1️⃣ 寻找下一个更大元素 2️⃣ 寻找前一个更大元素 3️⃣ 寻找下一个更小元素 4️⃣ 寻找前一个…

理性和感性 - 如何对待错误

上次的博客&#xff0c; 我写了一些关于 软件开发中的理性和感性决定 的故事。 不论是感性还是理性&#xff0c;我们的目的就是要把软件交给用户去用&#xff0c; 在软件行业中有这样一句俗话&#xff1a; 当你把产品交给用户的时候&#xff0c;你的学习才刚刚开始。 当然每个团…