Linux---進程信號

一、信號的概念

?信號是一種向目標進程發送通知消息的機制

信號的特性(可以結合紅綠燈、防空警報等生活樣例來理解)

1、在信號沒有出現之前,我們就已經知道如何去處理信號,即我們認識信號

2、信號是異步產生的,即我們不知道它具體何時產生

3、當信號產生時,我們可以對它暫時忽略不做處理(比如我們外賣到了,但是你正在和朋友開黑,就會將外賣暫時擱置)

4、由于我們有時不會立即去執行信號,所以我們需要能儲存信號

信號列表如下

一些補充的知識

二、信號的產生

?1、通過鍵盤進行信號的產生

解釋如下

該系統調用接口可以自定義捕捉信號的行為,將signum信號的默認行為改為handler

//process.cc
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>void handler(int signo)
{std::cout<<"發送了一個2號信號"<<std::endl;exit(1);
}int main()
{signal(2,handler);std::cout << "pid: " << getpid() << std::endl;while(1){std::cout << "running" << std::endl;sleep(1);}return 0;
}

對信號的進一步理解

?man 7 singal? 查看信號的默認行為

既然我們能通過signal系統調用將信號的默認方法改變,那么我們能否將所有能殺死進程的信號改掉,使得進程無法被終止呢???

?很顯然是不行的,有些信號是無法被自定義捕捉,比如9號信號,保證了OS的安全

2、通過系統調用進行信號的產生

功能:向pid進程發送sig信號(kill命令就是調用的該系統調用)

舉個例子(寫一個自己的kill命令)

//test.c
#include <stdio.h>
#include <unistd.h>
int main()
{printf("%d\n",getpid());while(1){printf("running\n");sleep(1);}return 0;
}//mykill.cc
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>int main(int argc,char* argv[])
{if(argc!=3){std::cout<<"\nUsage:" << argv[0] <<" -signnumber processid" << std::endl;return 0;}int signnumber = std::stoi(argv[1]+1);int pid = std::stoi(argv[2]);kill(pid, signnumber);return 0;
}

功能:向本進程發送sig信號

舉個例子

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>void handler(int signo)
{std::cout<<"發送了一個" << signo <<"號信號"<<std::endl;
}int main()
{signal(2,handler);while(1){raise(2);sleep(1);}return 0;
}

功能:向本進程發送6號信號,且進程一定會終止

舉個例子

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>void handler(int signo)
{std::cout<<"發送了一個" << signo <<"號信號"<<std::endl;
}int main()
{std::cout << getpid() << std::endl;signal(6,handler);// abort();while(1){std::cout << "running" << std::endl;sleep(1);}return 0;
}

3、通過硬件異常進行信號的產生

原理如下

那么除0異常是發送的哪個信號呢?是8號信號,驗證如下

根據上圖:發送的確實是8號信號,但現在還有一個問題,進程出異常,OS發送8號信號,可以理解,但是為什么它一直在打印呢,明明我的代碼沒有循環啊?

因為,原本的8號信號被自定義捕捉成了打印語句,導致進程無法退出,所以進程依舊會在等待隊列中等待CPU調度,所以各種寄存器中存放的該進程相關的數據(即進程上下文)都會被保留,包括狀態寄存器,而一旦該進程被調度,那么OS就又會檢測到硬件錯誤,向進程發送8信號,如此反復,故有上面的現象發生。

(一旦進程退出,它的相關數據就會被丟棄,因為我們不在需要調度該進程了)

OS殺死進程,就是處理問題的方式之一

程序運行出現異常,如何做取決于用戶,但一般都是要讓進程退出的(注意:異常的處理,很多時候只是打印錯誤)

順便說一下,*nullptr發送的信號是11號信號,本質是頁表中沒有該地址的物理地址的映射關系,引發的硬件錯誤

4、通過軟件條件進行信號的產生

這個其實在之前的博客中就講過一些示例,比如管道中只要讀端關閉,寫端還在寫,OS就會向寫端發送SIGPIPE,終止寫端,它本質是因為OS的內核數據中發現該管道只被一個進程打開,所以發信號終止寫端,不是硬件異常,而是軟件條件產生的信號。又比如調試程序用的gdb向進程發送的SIGSTOP和SIGCONT都是軟件條件產生的信號。

這里再介紹一個alarm函數

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
調用alarm函數可以設定一個鬧鐘,也就是告訴內核在seconds秒之后給當前進程發送SIGALRM信號(14號信號),該信號的默認處理動作是終止當前進程

?演示如下

上面兩段代碼都是設置了1秒的鬧鐘,確實時間到了,進程就終止了,但是兩段代碼cnt++的運行次數卻是天差地別,唯一的區別就是有沒有與外設進行IO交互,也證明了IO的效率很低

?

多次使用alarm函數,就是刷新鬧鐘,并返回之前鬧鐘剩余的時間,注意:alarm(0)不是設置0秒的鬧鐘,這個相當于清空之前的鬧鐘并返回之前鬧鐘剩余的時間

了解(擴展)---幫助理解alarm

這里講講alarm是如何實現的,首先每個進程都能設置鬧鐘,也就是說OS中可以存在多個鬧鐘,所以需要管理,即先描述在組織,所以我們要設計一個鬧鐘結構體,里面包含鬧鐘所屬進程id,時間(用時間戳記錄)等等(根據需求往里面加屬性)。然后就是如何組織,即選擇什么樣的數據結構進行管理,這里可以選擇用小堆,按照時間大小存放,如果時間堆頂鬧鐘的時間沒到,說明所有的都沒到,不做處理,如果時間到了,就拿出來處理。(當然這不一定是Linux中alarm的實現,這里只是提供思想,具體如何實現得根據需求)

core dump(核心轉儲)?

介紹:

分析core dump是Linux應用程序調試的一種有效方式,core dump又稱為“核心轉儲”,是該進程實際使用的物理內存的“快照”。分析core dump文件可以獲取應用程序崩潰時的現場信息,如程序運行時的CPU寄存器值、堆棧指針、棧數據、函數調用棧等信息。

Core dump是Linux基于信號實現的。Linux中信號是一種異步事件處理機制,每種信號都對應有默認的異常處理操作,默認操作包括忽略該信號(Ignore)、暫停進程(Stop)、終止進程(Terminate)、終止并產生core dump(Core)等

這里也簡單說一下,為什么有的信號終止進程需要核心轉儲,而有的不需要?

我們可以看一下那些不需要核心轉儲的終止信號,如SIGKILL,它其實并不是進程出現異常而將進程殺死,更類似于用戶強制終止進程,也就是說進程本身并沒有問題(或者出錯原因很明顯),所以我們不需要core dump再去分析異常,但是像SIGSEGV信號,即段錯誤信號,我們只能知道是內存出現問題,但是具體是數組越界還是其他什么問題引發的我們并不清楚,所以我們需要core dump幫助我們去分析。

但其實通過上面我們的示例代碼和結果截圖,我們會發現,Term/Core的功能好像都一樣,只是終止進程,并沒有產生core dump文件,這是什么原因呢?

因為核心轉儲的文件太大了,我們用的是服務器,默認將core dump大小設置為0,即不生成核心轉儲,防止服務器被寫滿(虛擬機應該是開啟的)當然可以通過指令打開,如下

注意:核心轉儲只能在對應的shell中生成,即哪個shell設置了core dump的大小,哪個shell跑程序收到異常才會生核心轉儲文件

上面的短短5行代碼,就需要生成55萬字節的文件,如果代碼在多一點,文件只會更大,所以為了保證服務的安全,系統默認將core dump文件大小設置為0

那么核心轉儲的文件有什么用呢?

三、信號存儲

信號的相關概念

  • 實際執行信號的處理動作稱為信號遞達(Delivery)
  • 信號從產生到遞達之間的狀態,稱為信號未決(Pending)---即在信號位圖中
  • 進程可以選擇阻塞 (Block )某個信號---未決之后,暫時不遞達

被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作
注意:阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之后可選的一種處理動作

如何在OS中體現上面說的三個概念---遞達、未決、阻塞??

  • 每個信號都有兩個標志位分別表示阻塞(block)和未決(pending),還有一個函數指針表示處理動作。信號產生時,內核在進程控制塊中設置該信號的未決標志,直到信號遞達才清除該標志。在上圖的例子中,SIGHUP信號未阻塞也未產生過,當它遞達時執行默認處理動作。
  • SIGINT信號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個信號,因為進程仍有機會改變處理動作之后再解除阻塞。
  • SIGQUIT信號未產生過,一旦產生SIGQUIT信號將被阻塞,它的處理動作是用戶自定義函數sighandler。如果在進程解除對某信號的阻塞之前這種信號產生過多次,將如何處理?POSIX.1允許系統遞送該信號一次或多次。Linux是這樣實現的:常規信號在遞達之前產生多次只計一次,而實時信號在遞達之前產生多次可以依次放在一個隊列里。不討論實時信號

四、信號阻塞

sigset_t

未決和阻塞標志可以用相同的數據類型sigset_t來存儲,sigset_t稱為信號集,這個類型可以表示每個信號的“有效”或“無效”狀態,在阻塞信號集中“有效”和“無效”的含義是該信號是否被阻塞,而在未決信號集中“有效”和“無效”的含義是該信號是否處于未決狀態。阻塞信號集也叫做當前進程的信號屏蔽字(Signal Mask),這里的“屏蔽”應該理解為阻塞而不是忽略

信號集操作函數

int sigemptyset(sigset_t *set);將所有信號的對應bit清零,表示該信號集不包含任何有效信號
int sigfillset(sigset_t *set);將所有信號的對應bit置1,表示該信號集的有效信號包括系統支持的所有信號
int sigaddset (sigset_t *set, int signo);在該信號集中添加某種有效信號
int sigdelset(sigset_t *set, int signo);在該信號集中刪除某種有效信號
int sigismember(const sigset_t *set, int signo);用于判斷一個信號集的有效信號中是否包含某種信號,若包含則返回1,不包含則返回0,出錯返回-1

注意:在使用sigset_ t類型的變量之前,一定要調用sigemptyset或sigfillset做初始化,使信號集處于確定的狀態

?sigprocmask

功能介紹:如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。如果set是非空指針,則更改進程的信號屏蔽字,參數how指示如何更改。假設當前的信號屏蔽字為mask,下表說明了how參數的可選值

SIG_BLOCKset包含我們希望添加到當前信號屏蔽字的信號,相當于mask = mask|set
SIG_UNBLOCKset包含我們希望從當前信號屏蔽字中解除阻塞的信號,相當于mask = mask&~set
SIG_SETMASK設置當前信號屏蔽字為set所指向的值,相當于mask=set

如果調用sigprocmask解除了對當前若干個未決信號的阻塞,則在sigprocmask返回前,至少將其中一個信號遞達

演示如下

當然可能有人會說,既然能屏蔽信號,我們能不能將所有的信號全部屏蔽???

當然不行,跟有些信號無法被自定義捕捉是一個道理,如9號信號,這里就不驗證了

?sigpending

讀取當前進程的未決信號集,通過set參數傳出。調用成功則返回0,出錯則返回-1

演示從阻塞到遞達的過程,如下

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>void handler(int signo)
{std::cout << "接收到" << signo << "信號" << std::endl;
}void Printf_Pending(const sigset_t &pending)
{for (int i = 31; i >= 1; i--){if (sigismember(&pending, i))std::cout << 1;elsestd::cout << 0;}std::cout << "\n";
}int main()
{std::cout << "pid: " << getpid() << std::endl;signal(2, handler);sigset_t set;sigemptyset(&set);sigaddset(&set, 2);sigprocmask(SIG_BLOCK, &set, nullptr); // 屏蔽2號信號std::cout << "屏蔽了2號信號" << std::endl;int cnt = 0;while (1){sigset_t pending;sigpending(&pending);Printf_Pending(pending);sleep(1);cnt++;if (cnt == 10){std::cout << "解除對2號信號的阻塞" << std::endl;sigprocmask(SIG_UNBLOCK, &set, nullptr);}}return 0;
}

這里有一個問題:pending位圖的置0操作和信號的遞達,誰先發生???

我們可以在handler方法中打印pending位圖,如果已經為0,則置0操作先發生,反之,相反

顯然,我們是先將pending位圖的對應信號比特位置0,在執行信號的遞達操作

五、信號的捕捉

信號在什么時候被處理?

--- 進程從內核態返回用戶態的時候,進行信號的檢測和遞達

  • 用戶態是一種受控的狀態,能夠訪問的資源是有限的
  • 內核態是一種OS的共工作狀態,能訪問大部分系統資源

系統調用的背后,就包含了身份的變化!!!

補充一些進程地址空間的內容

內核空間對應的頁表和OS資源只需要一份,因為所有的進程都需要,就像動態庫資源我們只要加載一份就行,需要的就去映射。所以CPU在任何時間都能訪問OS

可能有人會覺得:如果我的程序中只是在死循環的打印語句,沒有進行系統調用,那么信號是不是就被處理了?

當然不是,進程是要被OS調度切換的,而當進程被放在CPU上執行時,本質就已經完成了從OS(內核)到用戶的轉換,所以信號有無數次機會被檢測處理

Sigaction

#include <signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);

  • 功能:可以讀取和修改與指定信號相關聯的處理動作。調用成功返回0,出錯返回-1
  • 參數:signo是指定信號的編號。若act指針非空,則根據act修改該信號的處理動作。若oact指針非空,則通過oact傳出該信號原來的處理動作。act和oact指向sigaction結構體

(只關注被框出來的兩個成員)

將sa_handler賦值為常數SIG_IGN表示忽略信號,賦值為常數SIG_DFL表示執行系統默認動作,賦值為一個函數指針表示用自定義函數捕捉信號。和signal函數的第二個參數類似

當某個信號的處理函數被調用時,內核自動將當前信號加入進程的信號屏蔽字,當信號處理函數返回時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產生,那么它會被阻塞到當前處理結束為止。 如果在調用信號處理函數時,除了當前信號被自動屏蔽之外,還希望自動屏蔽另外一些信號,則用sa_mask字段說明這些需要額外屏蔽的信號,當信號處理函數返回時自動恢復原來的信號屏蔽字。sa_flags字段包含一些選項,這里我們不關心所以把sa_flags設為0,sa_sigaction是實時信號的處理函數

演示如下

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void Printf_Pending(const sigset_t &pending)
{for (int i = 31; i >= 1; i--){if (sigismember(&pending, i))std::cout << 1;elsestd::cout << 0;}std::cout << "\n";
}void handler(int signo)
{std::cout << "接收到" << signo << "信號" << std::endl;while (1){sigset_t pending;sigpending(&pending);Printf_Pending(pending);sleep(1);}
}int main()
{std::cout << "pid: " << getpid() << std::endl;struct sigaction act, oact;act.sa_handler = handler;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, 3);sigaction(2, &act, &oact);while (1)sleep(1);return 0;
}

顯然,sigaction函數不僅將自定義捕捉的2號信號在運行時自行屏蔽,而且可以通過sa_mask同時將其他的信號也屏蔽,注意這些屏蔽會在2號信號處理結束返回后解除

并且返回后,它還是會去檢測是否有信號需要被遞達,至于信號被處理的順序和信號優先級有關(這里就不演示了)

?六、信號的補充問題(了解)

可重入函數

即可以重復進入的函數,什么意思?舉個簡單的例子,我們學過用fork創建子進程,當父進程和子進程同時執行printf語句時,就有可能在屏幕上出現交替打印的情況,導致輸出的數據不符合預期,這就說明printf語句是不可重入函數。反之,如果多個執行流可以同時進入一個函數,且不發生錯誤,就是可重入函數。

注意:可重入和不可重入是函數的一個特征,并不能作為評判函數好壞的標準。一般來說,使用公共資源的函數都是不可重入函數。

?volatile---保持內存的可見性

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
int flag = 0;
void handler(int sigo)
{flag = 1;std::cout << "flag changed to :" << flag << std::endl;
}int main()
{signal(2, handler);std::cout << "flag:" << flag << std::endl;while (!flag);std::cout << "flag:" << flag << std::endl;return 0;
}

如果是正常編譯程序,不會有問題,一旦優化(相當于release版)就會出bug為什么?

因為flag變量被優化后直接放到了CPU的寄存器中,在信號處理時,我們改變的flag是內存中的flag,并不會改變寄存器中的flag,所以進程無法結束

我們可以給flag加volatile關鍵字,保證內存的可見性,即保證該變量一直從內存中讀取得到

SIGCHLD信號(17號信號)

子進程在終止時會給父進程發SIGCHLD信號,該信號的默認處理動作是忽略,父進程可以自 定義SIGCHLD信號的處理函數,這樣父進程只需專心處理自己的工作,不必關心子進程了,子進程終止時會通知父進程,父進程在信號處理函數中調用wait清理子進程即可

1、驗證子進程終止是否會向父進程發送SIGCHLD信號

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <sys/types.h>void handler(int signo)
{std::cout << "get " << signo << " sign" << std::endl;
}int main()
{signal(SIGCHLD, handler);pid_t id = fork();if(id==0){std::cout<<"child running"<<std::endl;sleep(5);exit(1);}wait(nullptr);return 0;
}

2、在handler函數中回收子進程

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <sys/types.h>void handler(int signo)
{std::cout << "get " << signo << " sign" << std::endl;wait(nullptr);
}int main()
{signal(SIGCHLD, handler);std::cout << "father pid: " << getpid() << std::endl;pid_t id = fork();if (id == 0){std::cout << "child pid: " << getpid() << std::endl;std::cout << "child running" << std::endl;sleep(5);exit(1);}return 0;
}

上面的是針對只有一個子進程的情況,但是如果有多個進程呢?

我們知道如果多個子進程同時退出,發送17號信號,父進程的pending表只會記錄一次17號信號,也就只會執行一次wait函數,所以有的子進程就會處于僵尸狀態,那我們該如何做?

有人可能覺得用循環就行,如下

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>void handler(int signo)
{std::cout << "get " << signo << " sign" << std::endl;pid_t id;while ((id = wait(nullptr))){std::cout << "child pid: " << id << " exit" << std::endl;if (id < 0)break;}
}int main()
{signal(SIGCHLD, handler);for (int i = 0; i < 5; i++){pid_t id = fork();if (id == 0){std::cout << "child is running" << std::endl;sleep(5);exit(1);}}while(1) sleep(1);return 0;
}

事實證明也確實行,但是這是所有子進程都結束的情況,如果有一部分子進程終止,另一部分子進程一直在運行,那么我們就無法跳出循環,因為wait是阻塞等待,它可以去查看是否有進程還沒終止,這樣就會一直在阻塞。

所以用非阻塞等待才是最恰當的,如下

void handler(int signo)
{std::cout << "get " << signo << " sign" << std::endl;pid_t id;while ((id = waitpid(-1, nullptr, WNOHANG)) > 0){std::cout << "child pid: " << id << " exit" << std::endl;}
}

當然,如果不關心子進程的返回信息,也可以直接忽略該信號,子進程會自動被清理。如下

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{signal(SIGCHLD, SIG_IGN);for (int i = 0; i < 5; i++){pid_t id = fork();if (id == 0){std::cout << "child is running" << std::endl;sleep(5);exit(1);}}while(1) sleep(1);return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/714382.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/714382.shtml
英文地址,請注明出處:http://en.pswp.cn/news/714382.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于MQTT協議實現微服務架構事件總線

一、場景描述 昨天在博客《客戶端訂閱服務端事件的實現方法》中提出了利用websocket、服務端EventEmitter和客戶端mitt實現客戶端訂閱服務端事件&#xff0c;大大簡化了客戶端對服務端數據實時響應的邏輯。上述方案適用于單服務節點的情形。 對于由服務集群支撐的微服務架構&…

Redis 之七:穿透、擊穿、雪崩

&#xff08;本內容部分來自知乎網等網絡&#xff09; Redis 緩存的使用&#xff0c;極大的提升了應用程序的性能和效率&#xff0c;特別是數據查詢方面。但同時&#xff0c;它也帶來了一些問題。其中&#xff0c;最要害的問題&#xff0c;就是數據的一致性問題&#xff0c;從嚴…

Educational Codeforces Round 132 (Rated for Div. 2) E. XOR Tree(啟發式合并+貪心)

題目 n(n<2e5)個點的樹&#xff0c;點i權值ai&#xff08;1<ai<2^30&#xff09; 修改最少的點的權值&#xff0c;使得樹上不存在異或和為0的簡單路徑&#xff0c;輸出最少的點數 權值可以被修改成任意正整數&#xff08;可以是無限大&#xff09; 思路來源 官方…

【leetcode】環形鏈表?環形鏈表II

大家好&#xff0c;我是蘇貝&#xff0c;本篇博客帶大家刷題&#xff0c;如果你覺得我寫的還不錯的話&#xff0c;可以給我一個贊&#x1f44d;嗎&#xff0c;感謝?? 目錄 1.環形鏈表解題拓展&#xff1a; 2.環形鏈表II 1.環形鏈表 點擊查看題目 解題 思路: bool hasCycle…

【算法集訓】基礎算法:基礎排序 - 插入排序

一、基本理解 插入排序(nsertion Sort)&#xff0c;一般也被稱為直接插入排序&#xff0c;是一種簡單直觀的排序算法。 **工作原理&#xff1a;**將待排列元素劃分為「已排序」和「未排序」兩部分&#xff0c;每次從「未排序的」元素中選 擇一個插入到「已排序的」元素中的正確…

劍指offer58—II 左旋轉字符串 c++

題目 字符串的左旋轉操作是把字符串前面的若干個字符轉移到字符串的尾部。請定義一個函數實現字符串左旋轉操作的功能。比如,輸入字符串"abcdefg"和數字2,該函數將返回左旋轉兩位得到的結果"cdefgab"。 示例 1: 輸入: s = “abcdefg”, k = 2 輸出: “…

MySQL 多表查詢 連接查詢 內連接

介紹 內連接查詢是兩張表中交集的部分 連接模式 隱式內連接 SELECT 字段列表 FROM 表1,表2 WHERE 條件顯式內連接 SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 連接條件案例 有兩張表一個表為學生表&#xff0c;另一個表為班級表&#xff0c;現在需要查詢學生時候在查…

接口測試(全)

&#x1f345; 視頻學習&#xff1a;文末有免費的配套視頻可觀看 &#x1f345; 關注公眾號【互聯網雜貨鋪】&#xff0c;回復 1 &#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快 大多數人對于接口測試都覺得是一種高大上的測試&#xff0c;覺得…

羊大師分析,羊奶粉適合什么樣的人群喝

羊大師分析&#xff0c;羊奶粉適合什么樣的人群喝 羊奶粉適合多種人群食用&#xff0c;包括兒童、老年人、孕婦以及身體虛弱或處于疾病康復期的人群。 對于兒童來說&#xff0c;羊奶粉是一種很好的營養品。它含有豐富的蛋白質、脂肪、礦物質和維生素&#xff0c;能夠滿足兒童…

【前端素材】推薦優質后臺管理系統網頁Star admin平臺模板(附源碼)

一、需求分析 1、系統定義 后臺管理系統是一種用于管理和控制網站、應用程序或系統的管理界面。它通常被設計用來讓網站或應用程序的管理員或運營人員管理內容、用戶、數據以及其他相關功能。后臺管理系統是一種用于管理網站、應用程序或系統的工具&#xff0c;通常由管理員使…

三種圖片預覽插件viewer、vue-photo-preview、vue-picture-preview

第一種&#xff1a;viewerjs使用介紹 1、先安裝依賴 npm install v-viewer --save2、main.js內引用并注冊調用 //main.js import Viewer from ‘v-viewer’ import ‘viewerjs/dist/viewer.css’ Vue.use(Viewer); Viewer.setDefaults({ Options: { “inline”: true, “butt…

王志亮出席海爾智慧樓宇發酵行業的低碳節能解決方案

演講嘉賓&#xff1a;王志亮 食品醫藥用戶群總監 青島海爾空調電子有限公司 演講題目&#xff1a;海爾智慧樓宇在發酵行業的低碳、節能解決方案 會議簡介 “十四五”規劃中提出&#xff0c;提高工業、能源領城智能化與信息化融合&#xff0c;明確“低碳經濟”新的戰略目標&…

System Verilog學習筆記(十一)——數組(1)

System Verilog學習筆記&#xff08;十一&#xff09;——數組&#xff08;1&#xff09; 非組合型&#xff08;unpacked&#xff09; 成員之間存儲數據都是相互獨立的可以索引非組合型數組或者數組片段的能力聲明方式&#xff1a; logic [31&#xff1a;0] data [1024]; lo…

黑馬JUC筆記

黑馬JUC筆記 1.概覽 2.進程與線程 2.1 進程與線程 進程 程序由指令和數據組成&#xff0c;但這些指令要運行&#xff0c;數據要讀寫&#xff0c;就必須將指令加載至 CPU&#xff0c;數據加載至內存。在 指令運行過程中還需要用到磁盤、網絡等設備。進程就是用來加載指令、管…

Cisco Secure ACS 5.8.0.32 安裝 + Crack 教程

Cisco Secure ACS 5.8.0.32 安裝 Crack 教程 前言系統環境開始安裝 開始破解導入授權文件 前言 在ESXi 6.7 上經歷過無數次的安裝嘗試 測試了各種兼容版本都沒有安裝成功,記最后一次安裝成功的過程. 系統環境 服務器 : Dell R720xd CPU : E5-2620 v2 系統 : ESXi 6.7…

簡單控件屬性設置

1、設置文本的內容 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"…

十四、Qt主機信息與網絡編程

一、主機信息 1、主機信息接口 QHostInfo&#xff1a;獲取主機名稱和IP地址QNetWorkInterface&#xff1a;獲取主機的所有網絡接口&#xff0c;包括子網掩碼和廣播地址等 &#xff08;1&#xff09;使用 項目添加模塊QT network2、實現程序 &#xff08;1&#xff0…

【01】openEuler 源碼安裝 PostgreSQL

openEuler 源碼安裝 PostgreSQL 部署環境說明Shell 前端軟件包管理器基礎概念YUM 簡介DNF 簡介 源碼安裝 PostgreSQL環境變量&#xff08;env&#xff09;設置臨時環境變量設置永久環境變量設置 初始化數據庫&#xff08;initdb&#xff09; 數據庫基本操作數據庫基本配置&…

WiFi協議的調制技術介紹

調制技術是WiFi協議的核心部分&#xff0c;它負責將數據轉換成可以在無線信道中傳輸的信號。WiFi協議采用正交頻分復用&#xff08;OFDM&#xff09;調制技術&#xff0c;該技術通過將數據分成多個子載波進行傳輸&#xff0c;提高了信道利用率和抗干擾能力。 OFDM調制的工作原…

推特API(Twitter API)V2 用戶關注

前面章節已經介紹使用code換取Token的整個流程了&#xff0c;這里不再重復闡述了&#xff0c;下面我們獲取到用戶token以后如何幫用戶自動關注別人。需要參數關注者的用戶ID&#xff08;token授權用戶&#xff09;以及關注的目標用戶ID。用戶ID如何獲取可以看上一章節獲取用戶信…