Linux程序和執行緒
目錄
程序
概念
程序:動態概念(執行中程式)
程式:靜態(一些指令集合)程式檔案(可以移動)
軟體:文件加程式
程序:程序是系統分配資源的最小單位
程序控制(程序狀態)動態--生命週期 建立--排程--銷燬
程序建立fork--系統呼叫
pid_t fork(void)
返回值
<0建立失敗
==0現在所處的程序是子程序
>0 現在所處的程序是父程序(子程序的id號)
fork的兩種用法
(1)一個父程序希望複製自己,使父,子程序同時執行不同程式碼段。這在網路服務程序中是常見的--父程序等待客戶端的服務請求。當這種請求到達時,父程序呼叫fork,使子程序處理此請求。父程序則等待下一個服務請求到達
(2)一個程序執行一個不同的程式。這對shell是常見的情況。在這種情況下,子程序從fork返回後立即呼叫exec
每一次fork都會產生一個子程序,而wait(),相當於遞迴一樣,等待最後一個子程序死後才去列印它上一個程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
pid_t x;
int i;
for(i=0; i<10; i++)
{
x = fork();
if(x > 0)
break;
if(x == 0)
continue;
}
wait(NULL);
printf("PID %d PPID %d\n", getpid(), getppid());
return 0;
}
vfork---先執行子程序,(子程序退出後)執行父程序
注意:一定要在子程序裡要用exit()來退出子程序,不然會進入無限的迴圈。
fork()與vfork()的區別~~~:>>
主要為兩點:
(1)執行次序:fork():對父子程序的排程室由排程器決定的;
vfork():是先呼叫子程序,等子程序的exit(1)被呼叫後,再呼叫父程序;
(2)對資料段的影響:fork():父子程序不共享一段地址空間,修改子程序,父程序的內容並不會受影響。
vfork():在子程序呼叫exit之前,它在父程序的空間中執行,也就是說會更改父程序的資料段、 棧和堆。。即共享程式碼區和資料區,且地址和內容都是一樣的。
程序ID
每一個程序都有一個非負數整型表示的唯一ID。
getpid()--獲取當前程序的id號
getppid()--獲取當前程序的父程序id
getuid()--呼叫程序使用者的實際ID
geteuid()--呼叫程序的有效使用者ID
getgid()---呼叫程序實際組ID
getegid()--呼叫程序的有效組ID
殭屍程序
父程序還執行子程序已經退出(但是父程序沒有回收子程序資源)(程式設計時候必須避免)
父程序回收子程序資源 在父程序中呼叫wait
pid_t wait(int *status)
孤兒程序
父程序已經退出子程序還在執行(最後都會把init程序作為父程序)
操作過程:在一個程序終止時,核心逐個檢查所有活動程序,以判斷它是否是正在終止的子程序,如果是,則將該程序的父程序ID更改為1(init的程序ID)
程序退出
正常退出(程式執行完畢)
呼叫exit退出 (會做善後處理,關閉檔案,清空快取)
void exit(int status);會關閉所有IO流,進行沖洗
呼叫_exit退出 (直接退出) void _exit(int status);
status:子程序的退出值
無返回值
等待子程序
程序一旦呼叫了wait,就立即阻塞自己,由wait自動分析是否當前程序的某個子程序已經退出。如果讓它找到了這樣一個已經變成殭屍的子程序,wait就會收集這個子程序的資訊,並把它徹底銷燬後返回;如果沒有找到這樣一個子程序,wait就會一直阻塞在這裡,直到有一個出現為止。
pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options);
WNOHANG:非阻塞等待
WCONTINUED:報告任意一個從暫停態出來且從未報告的子程序的狀態
WUNTRQCED:報告任意一個當前處於暫停態且從未報告的子程序的狀態
子程序的結束狀態值會由引數status返回,而子程序的程序PID也會一起返回,如果不在意結束狀態,則引數status可以設定為NULL。如果執行成功則返回程序PID,如果失敗則返回-1
waipid:如果想讓父程序週期性檢查某個特定的子程序是否已經終止,可以使用waitpid(child_pid,(int *)0,WNOHANG)
exec函式族(接管一個程序的所有資源)
int execl(const char *path, const char *arg, ...); 後面變參必須以NULL結束(類似於main函式引數)
execl("/bin/ls", "ls","-a", "-l", NULL); ls -a -l 必須把要執行的程式路徑加到PATH環境變中
int execlp(const char *file, const char *arg, ...);
execl("ls", "ls","-a", "-l", NULL); ls -a -l
int execle(const char *path, const char *arg,..., char * const envp[]);
char *envp[] = {"MM=/home/gec", "/usr/local", NULL};
execle("/home/gec/myshare/main", "main", NULL, envp);
int execv(const char *path, char *const argv[]);
char *argv[] = {"ls","-a", "-l", NULL};
execv("/bin/ls", argv);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
system("/home/gec/main")---類似於函式呼叫
exec函式族成功執行後,原有的程式程式碼都將被指定的檔案或指令碼覆蓋,,後面的程式碼無法執行,也無法返回
守護程序
是指在後臺執行且不與終端相連街的一種程序(也稱之為精靈程序或後臺程序)
精靈程序示例(將一句話寫到日記檔案裡)
daemon.h
#ifndef __DAEMON_H
#define __DAEMON_H
#include <stdio.h>
#include <syslog.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
int daemon_init(void);
#endif
daemon.c
#include "daemon.h"
int daemon_init(void)
{
pid_t pid;
int fd0, fd1, fd2, max_fd,i;
// 1
if((pid = fork()) < 0)
{
perror("fork faild!");
exit(1);
}
else if(pid != 0)//讓父程序能出
{
exit(0);
}
// 2
signal(SIGHUP, SIG_IGN);//忽略終端被關閉的訊號
// 3
if(setsid() < 0) //建立一個新的會話
{
exit(1);
}
// 4
if((pid = fork()) < 0)
{
perror("fork faild!");
exit(1);
}
else if(pid != 0)
exit(0);
// 5
setpgrp(); //設定孫子程序的程序組,徹底脫離會話
// 6 關閉看有的檔案描述符,預設是1024個,但沒有那麼多開啟,那麼close就沒反應的
max_fd = sysconf(_SC_OPEN_MAX);//獲取檔案開啟的最大個數
for (i = max_fd; i>=0; i--)
{
close(i);//關閉檔案
}
// 7 ; 0:表示不會影響你設定的許可權,當你是umask(0777)的話,你要設定的檔案許可權都會被減為0,沒有許可權的意思
umask(0);
// 8 修改守護程序的當前工作路徑,讓它掛載到跟目錄不會被解除安裝
chdir("/");
//Initialize the log file. 開啟日記檔案
openlog("daemon_test", LOG_CONS | LOG_PID, LOG_DAEMON);
return 0;
}
main.c
#include <unistd.h>
#include "daemon.h"
int main(void)
{
daemon_init(); //初始化一個精靈程序
while(1)
{
syslog(LOG_DAEMON, "I am a daemonAAA!"); //將這句話寫到日記檔案裡
sleep(2); //2S寫一次,但日記裡只會出現一次,下面會告訴你重複了多少次
}
return 0;
}
程序間通訊
程序間通訊 管道, 訊號, 訊息佇列,訊號量,記憶體共享
管道
無名管道
無名管道--只使用於親屬程序(父子程序)
PIPE的特徵:
1、沒有名字,因此無法使用open() (只能在一個程序中被創建出來,通過繼承方式將它的檔案描述符傳遞給子程序,這也是 PIPE只能用於親緣程序的原因)
2、 只能用於親緣程序間的通訊(父子程序,兄弟程序,祖孫程序)
3、 半雙工工作方式:讀寫端分開
4、 寫入操作不具有原子性,因此只能用於一對一的簡單通訊情型
5、 不能使用lseek()來定位(因為它的資料不像普通檔案那樣按塊的方式存放在如硬碟,Flash等塊裝置上)
建立無名管道
int pipe(int pipefd[2]);
從pipefd[1]寫入,從pipefd[0]讀出
程式碼:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd[2];//用於存放PIPE的兩個檔案描述符
int ret = pipe(fd);
if(ret < 0)
{
perror("create pipe failed \n");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
perror("create fork failed\n");
}
if(pid == 0)
{
write(fd[1],"hello world",12);
}
if(pid > 0)
{
char buf[12] = {0};
read(fd[0],buf,12);
printf("%s\n",buf);
}
close(fd[0]);
close(fd[1]);
return 0;
}
管道和exec函式
pipe2.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
int processed;
int fd[2];
const char data[] = "123";
char buf[BUFSIZ+1];
memset(buf,'\0',sizeof(buf));
int ret = pipe(fd);
if(ret < 0)
{
perror("create pipe failed\n");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
perror("create fork failed\n");
return -1;
}
if(pid == 0)
{
//把讀取管道資料的檔案描述符儲存到一個快取區中,
//該快取區的內容將構成pipe3程式的一個引數
sprintf(buf,"%d",fd[0]);
//(char *)0的作用是終止被呼叫程式的引數列表
(void)execl("pipe3","pipe3",buf,(char *)0);
exit(EXIT_FAILURE);
}
if(pid > 0)
{
processed = write(fd[1],data,strlen(data));
printf("%d --wrote %d bytes\n",getpid(),processed);
}
return 0;
}
pipe3.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv)
{
int processed;
char buf[BUFSIZ+1];
int descriptor;
memset(buf,'\0',sizeof(buf));
sscanf(argv[1],"%d",&descriptor);
processed = read(descriptor,buf,BUFSIZ);
printf("%d --read %d bytes: %s\n",getpid(),processed,buf);
return 0;
}
有名管道
有一個實實在在的管道檔案-
FIFO的特徵
1、有名字,儲存於普通檔案系統之中
2、任何具有相應許可權的程序都可以使用open()來獲取FIFO的檔案描述符(程式不能以O_RDWR模式開啟FIFO檔案進行讀寫操作)
3、跟普通檔案一樣,使統一的read()/write()來讀/寫
4、跟普通檔案不同,不能使用lseek()來定位,原因同PIPE
5、具有寫入原子性,支援多寫者同時進行寫操作而資料不會被踐踏
6、 First in First out 最先被寫入FIFO的資料,最先被讀出
建立管道 mkfifo 檔名
int mkfifo(const char *pathname, mode_t mode);
管道的讀寫
讀操作read
有寫者===》有資料===》 正常讀取
===》無資料===》 阻塞等待
無寫者===》有資料===》 正常讀取
===》無資料===》立即返回
寫操作write
有讀者===》快取未滿===》正常寫入
快取已滿===》阻塞等待
無讀者===》快取未滿===》立即收到SIGPIPE
===》 快取已滿===》 立即收到SIGPIPE
FIFO間通訊程式碼
fifoA.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define FIFOPATH "/tmp/fifo"
int main(int argc, char **argv)
{
int ret = mkfifo(FIFOPATH, 00777);
if(ret < 0 && errno != EEXIST)
{
perror("create fail");
return -1;
}
//
int fd = open(FIFOPATH, O_RDWR);
if(fd < 0)
{
perror("open fifo fail");
return -1;
}
char buf[32] = {0};
while(1)
{
scanf("%s", buf);
ret = write(fd, buf, strlen(buf)+1);
if(ret <= 0)
{
perror("write fail");
break;
}
if(strcmp(buf, "quit") == 0) break;
}
close(fd);
return 0;
}
fifoB.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define FIFOPATH "/tmp/fifo"
int main(int argc, char **argv)
{
int ret = mkfifo(FIFOPATH, 00777);
if(ret < 0 && errno != EEXIST)
{
perror("create fail");
return -1;
}
//
int fd = open(FIFOPATH, O_RDWR);
if(fd < 0)
{
perror("open fifo fail");
return -1;
}
char buf[32] = {0};
while(1)
{
ret = read(fd, buf, 32);
if(ret <= 0)
{
perror("write fail");
break;
}
if(strcmp(buf, "quit") == 0) break;
printf("wwww%s\n", buf);
}
close(fd);
return 0;
}
popen()和pclose()
FILE *popen(const char *command, const char *type);
popen函式允許一個程式將另一個程式作為新程序來啟動,並可以傳遞資料給它或者通過它接收資料
引數
command:字串是要執行的程式名和相應的引數
type:
“r”:被呼叫的程式輸出就可以被呼叫程式使用,呼叫程式利用popen函式返回的FILE*檔案流指標,就可以通過常用的stdio庫函式 (如fread)來讀取被呼叫程式的輸出
“w”:呼叫程式就可以用fwrite嗲用向被呼叫程式傳送資料,而被呼叫程式就可以在自己的標準輸入上讀取這些資料,被呼叫的程 序 通常不會意識到自己正從另一個程序讀取資料,它只是在標準輸入流上讀取資料,然後做出相應的操作
int pclose(FILE *stream);
讀取外部程式的輸出:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
FILE *read_fp;
char buffer[BUFSIZ+1];
int chars_read;
memset(buffer,'\0',sizeof(buffer));
read_fp = popen("ls -l","r");
if(read_fp != NULL)
{
chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp);
if(chars_read > 0)
{
printf("output was:-\n%s\n",buffer);
}
pclose(read_fp);
exit(EXIT_SUCCESS);
}
return 0;
}
將讀出送往外部程式:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
FILE *write_fp;
char buffer[BUFSIZ+1];
sprintf(buffer,"Once upon a time,threr was...\n");
write_fp = popen("od -c","w");
if(write_fp != NULL)
{
fwrite(buffer,sizeof(char),strlen(buffer),write_fp);
pclose(write_fp);
exit(EXIT_SUCCESS);
}
return 0;
}
管道的弊端
無法在管道中讀取一個指定的資料,因為這些資料沒有做任何標記,讀者程序只能按次序地逐個讀取
訊號
傳送和接受訊號
kill()和signal()
傳送訊號kill:
訊號攔截signal
1、 sighandler_t signal(int signum, sighandler_t handler);(攔截的訊號, 攔截後要做的事情)
2、 typedef void (*sighandler_t)(int);
(升級板)sigqueue()和sigaction()
struct sigaction的結構體
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
程式碼:
void ouch(int sig)
{
printf("ouch %d",sig);
}
int main(int argc, char **argv)
{
struct sigaction act;
act.sa_handler = ouch;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT,&act,NULL);
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
sigaction.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <strings.h>
void f(int sig, siginfo_t *info, void *arg)
{
printf("sig: %d\n", sig); // SIGINT(2)
printf("int: %d\n", info->si_int); // a.sival_int
printf("from %d\n", info->si_pid);
}
int main(void)
{
pid_t x = fork();
if(x > 0) // 父程序
{
struct sigaction act;
bzero(&act, sizeof(act));
act.sa_sigaction = f;
act.sa_flags = SA_SIGINFO; // 我要用擴充套件版的響應函式
// 向核心申請,將來收到SIGINT訊號的時候,按照act結構體中
// 描述的情況來處理。NULL表示不需要核心返回該訊號原來的處理模式
sigaction(SIGINT, &act, NULL); // signal()的擴充套件版
pause();
}
if(x == 0) // 子程序
{
union sigval a;
a.sival_int = 100;
printf("child PID: %d\n", getpid());
sleep(1);
// 給父程序傳送訊號SIGINT,順便還發了a這個值
sigqueue(getppid(), SIGINT, a); // kill()的擴充套件版
}
}
訊號響應
1、預設動作
p1:int main(int argc, char **argv)
{
printf("my PID %d\n",getpid());
pause();
return 0;
}
p2:int main(int argc, char **argv)
{
pid_t pid;
scanf("%d",&pid);
kill(pid,SIGINT);
return 0;
}
通過p2傳送訊號將p1殺死
2、忽略
p1:int main(int argc, char **argv)
{
printf("my PID %d\n",getpid());
signal(SIGINT,SIG_IGN);
pause();
return 0;
}
p2:int main(int argc, char **argv)
{
pid_t pid;
scanf("%d",&pid);
kill(pid,SIGINT);
return 0;
}
p2向p1傳送殺死訊號,沒有反應,因為p1已經忽略該訊號 SIGKILL和SIGSTOP都無法忽略
3、捕捉
p1:void f(int sig)
{
printf("catch sig:%d\n",sig);
}
int main(int argc, char **argv)
{
printf("my PID %d\n",getpid());
signal(SIGINT,f);
pause();
return 0;
}
p2:int main(int argc, char **argv)
{
pid_t pid;
scanf("%d",&pid);
kill(pid,SIGINT);
return 0;
}
f()函式由核心呼叫,函式預先告訴核心,等接受到SIGINT訊號時,呼叫f()函式
4、阻塞
程式碼
void catch(int sig)
{
fprintf(stderr,"%d catch SIGQUIT\n",getpid());
}
int main(int argc, char **argv)
{
signal(SIGQUIT,catch);
sigset_t sig;
sigemptyset(&sig);
sigaddset(&sig,SIGQUIT);
sigprocmask(SIG_BLOCK,&sig,NULL);
pid_t pid;
pid = fork();
if(pid == 0)
{
fprintf(stderr,"child %d\n",getpid());
int i = 10;
while(i>0)
{
printf("%d\n",i--);
sleep(1);
}
sigprocmask(SIG_UNBLOCK,&sig,NULL);
while(1)
pause();
}
if(pid > 0)
{
fprintf(stderr,"parent %d\n",getpid());
sigprocmask(SIG_UNBLOCK,&sig,NULL);
while(1)
pause();
}
return 0;
}
因為SIGQUIT被阻塞了,子程序接收到SIGQUIT是沒到反應,等到10秒後解除阻塞才有反應,而父程序一開始就已經解除阻塞
其他
檢視或刪除系統中的IPC物件
獲取一個當前未用的IPC的key
訊息佇列MSG
什麼是訊息佇列
訊息佇列提供一種帶有資料標識的特殊管道,使用一段被寫入的資料都變成帶標識的訊息,讀取該段訊息的程序只要指定這個標識就可以正確的讀取,而不受到其他訊息的干擾
訊息佇列使用方法
(1)傳送者
a、 獲取訊息佇列的ID int msgget(key_t key, int msgflg);
b、 將資料放入一個附帶有標識的特殊的結構體,傳送給訊息佇列
(2)接收者
A、 獲取訊息佇列的ID
B、將指定標識的訊息讀出
(3 )設定或獲取訊息佇列的相關屬性
1、int msgctl(int msqid, int cmd, struct msqid_ds *buf);
2、msqid:訊息佇列ID
3、cmd:
IPC_STAT:獲取該MSG的資訊,儲存在結構體msqid_ds中
IPC_SET:設定該MSG的資訊,儲存在結構體msqid_ds中
IPC_RMID:立即刪除該MSG,並喚醒所有阻塞在該MSG上的程序,同時忽略第三個引數
IPC_INFO:獲取關於當前系統中MSG的限制值資訊
MSG_INFO:獲取關於當前系統中MSG的相關資源消耗資訊
MSG_STAT:同IPC_STAT,但msgid為該訊息佇列的核心中記錄所有資訊的陣列的下標,因此通過迭代所有的下標可以獲得系統 中所有訊息佇列的相關資訊
4、buf:相關資訊結構提的緩衝區
5、IPC_STAT獲取的屬性資訊被存放在一下結構體中
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
6、其中,許可權相關的資訊用如下結構體來表示
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
(4)刪除訊息佇列 msgctl(msgid,IPC_RMID,NULL);
使用步驟
1.設定一個key值
2.在核心空間中申請佇列(id)
3.寫資料
4.讀資料
5.銷燬
共享記憶體段SHM
什麼是共享記憶體
多個程序可以把一段記憶體對映到自己的程序空間,以此來實現資料的共享以及傳輸,這也是所有程序間通訊方式中最快的一種
共享記憶體的使用步驟
1、獲取共享記憶體物件的ID
int shmget(key_t key, size_t size, int shmflg);
key:共享記憶體的鍵值
size:共享記憶體的尺寸(PAGE_SIZE的整數倍)
shmflg:
IPC_CREAT:如果key對應的共享記憶體不存在,則建立
IPC_EXCL:如果該key對應的共享記憶體已存在,則報錯
SHM_HUGETLB:使用大頁面在分配共享記憶體
SHM_NORESERVE:不在交換分割槽中為這塊共享記憶體保留空間
mode:共享記憶體的訪問許可權(八進位制,如0644)
返回值
成功:該記憶體的ID
失敗:-1
備註:如果key指定為IPC_PRVATE,則會自動產生一個隨機未用的新鍵值
2、將共享記憶體對映至本程序虛擬記憶體空間的某個區域
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);解除對映 shmaddr共享記憶體的首地址
shmid:共享記憶體ID
shmaddr:
(1)如果為NULL,則系統會自動選擇一個合適的虛擬記憶體空間地址去對映共享記憶體
(2)如果不為NULL,則系統會根據shmaddr來選擇一個合適的記憶體區域
shmflg:
SHM_RDONLY以只讀的方式對映共享記憶體
SHM_REMAP:重新對映,此時shmaddr不能為NULL
SHM_RND:自動選擇比shmaddr小的最大頁對齊地址
可用0,表示對共享記憶體可讀可寫
第二引數被設定,那麼第三個引數設定為SHM_RND,
返回值
成功:共享記憶體的首地址
失敗:-1
3、當不在使用是,解除對映關係
4、當沒有程序再需要這塊共享記憶體時,刪除它
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:共享記憶體ID
cmd:
IPC_STAT:獲取屬性資訊,放到buf中
IPC_SET:設定屬性資訊為buf指向的內容
IPC_RMID:將共享記憶體標記為“立即被刪除”狀態
IPC_INFO:獲取關於共享記憶體的限制值資訊
SHM_INFO:獲取系統為共享記憶體消耗的資源資訊
SHM_STAT:同IPC_STAT,但shmid為該SHM在核心中記錄所有SHM資訊的陣列下標,因此通過迭代所有下標可以獲得系統中所有 SHM的相關資訊
SHM_LOCK:禁止系統將該SHM交換至swap分割槽
SHM_UNLOCK:允許系統將該SHM交換至swap分割槽
返回值
成功:
IPC_INFO:核心中記錄所有SHM資訊的陣列的下標最大值
SHM_INFO:核心中記錄所有SHM資訊的陣列的下標最大值
SHM_STAT:x下標值為shmid的SHM的ID
失敗:-1
IPC_STAT獲得的屬性資訊被存放在以下結構體中:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
其中許可權資訊結構體:
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
5、可以通過shmctl獲取或設定共享記憶體的相關屬性
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:共享記憶體的ID
cmd:要採取的動作
常用的值
IPC_STAT:獲取屬性資訊,放到buf中
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
IPC_SET:設定屬性為buf指向的內容
IPC_RMID:刪除共享記憶體段
buf:屬性資訊結構體指標
程式碼:
shmA.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
//1.獲取key值
key_t key = ftok("/", 100);
printf("key = %d\n", key);
//2.獲取共享記憶體id
int shmid = shmget(key, 1024, IPC_CREAT|0777);
if(shmid < 0)
{
perror("get id fail");
return -1;
}
//3.對映--連結
char *p = shmat(shmid, NULL, 0);
memset(p, 0, 1024);
memcpy(p, "hello haha", 11);
getchar();
//4.釋放對映
shmdt(p);
//5.獲取共記憶體狀態
struct shmid_ds shmbuf;
int ret = shmctl(shmid, IPC_STAT, &shmbuf);
if(ret < 0)
{
perror("獲取狀態失敗");
}
//當對映該SHM的程序個數為0時,刪除共享記憶體
if(shmbuf.shm_nattch == 0)
{
shmctl(shmid, IPC_RMID, NULL);
}
return 0;
}
shmB.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
//1.獲取key值
key_t key = ftok("/", 100);
printf("key = %d\n", key);
//2.獲取共享記憶體id
int shmid = shmget(key, 1024, IPC_CREAT|0777);
if(shmid < 0)
{
perror("get id fail");
return -1;
}
//3.對映--連結
char *p = shmat(shmid, NULL, 0);
if(p == NULL)
{
perror("shmat fail");
}
printf("p=%s", p);
getchar();
}
訊號量SEM
1、概念
訊號量的P,V操作最核心的特徵是:它們是原子性的,也就是說對訊號量元素的值的增加和減少,系統保證CUP的電氣特性級別上不可分割,這跟整型資料的加減法有本質的區別
2、 獲取訊號量的ID
int semget(key_t key, int nsems, int semflg);
key:訊號量的鍵值
nsems:訊號量的個數
semflg:
IPC_CREAT:如果key對應的訊號量不存在,則建立
IPC_EXCL:如果key對應的訊號量存在,則報錯
mode:訊號量的訪問許可權
3、對訊號量進行P/V操作,或等零操作
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid:訊號量的ID(是由semget返回的訊號量識別符號)
sops:訊號量操作結構體陣列
struct sembuf
{
unsigned short sem_num; /* 訊號量的元素序號(陣列下標)(除非需要使用一組訊號量,否則它的取值一般為0) */
short sem_op; /* 操作元素(通常只會用到兩個值,一個是-1,也就是P操作,它等待訊號量變為可用,一個是+1,也就是V操作,它傳送訊號表示訊號量現在已可用) */
short sem_flg; /* 操作選項 */
}
nsops:結構體陣列元素個數
4、 獲取或設定訊號量的相關資訊
int semctl(int semid, int semnum, int cmd, ...);
semid:訊號量ID
semmum:訊號量元素序號(當需要用到成組的訊號量是,就需要用到這個數,它一般取值為0,表示這是第一個也是唯一一個訊號量)
cmd:將要採取的動作
兩個常用的值
SETVAL:用來把訊號量初始化為一個已知的值,這是通過union semun中的val成員設定,其作用是咋訊號量第一次使用之前對它進行設定
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
IPC_RMID:用於刪除一個已經無需繼續使用的訊號量識別符號
執行緒
1、概念
執行緒是系統排程的最小單位。
程序資源(程序間資料獨立) 執行緒資源(一個程序中的多個子執行緒是共用程序資源)(資料段, 堆)執行緒有自己的獨立棧空間
2、建立一條新的執行緒
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
thread:新執行緒的TID 用pthread_t ID名來建立
attr:執行緒屬性(執行緒屬性如果為NULL,則會建立一個標準屬性的執行緒,)
start_routine:執行緒例程(執行緒例程指的是如果執行緒建立成功,那麼該執行緒會立即去執行的函式)
arg:執行緒的例程引數
返回值
成功:0
失敗:errno
程式碼
struct student
{
char name[20];
int id;
int phone;
};
void * function(void *arg)
{
struct student *al = ((struct student *)arg);
while(1)
{
printf("name =%s id = %d phone = %d\n",al->name,al->id,al->phone);
sleep(1);
}
}
int main(int argc, char **argv)
{
pthread_t pthread;
struct student arg;
strcpy(arg.name,"wangyisi");
arg.id = 883;
arg.phone = 123456;
int ret = pthread_create(&pthread,NULL,function,(void *)&arg);
if(ret !=0)
{
perror("pthread_ceate fail\n");
exit(1);
}
while(1)
{
printf("wait----\n");
sleep(1);
}
return 0;
}
3、執行緒屬性函式
執行緒屬性變數的使用步驟
(1)定義執行緒屬性變數,並使用pthread_attr_init初始化
int pthread_attr_init(pthread_attr_t *attr);
(2)使用pthread_attr_setXXX來設定相關的屬性
(3)使用該執行緒屬性變數建立相應的執行緒
(4)使用pthread_attr_destroy()銷燬該執行緒屬性變數
int pthread_attr_destroy(pthread_attr_t *attr);
設定執行緒的分離屬性
1、一條執行緒如果可接合,意味著這條執行緒在退出時不會自動釋放自身資源,而會成為殭屍程序,同時意味著該執行緒退出值可以 被 其他執行緒取代,因此,如果不需要某條執行緒的退出值,那最好將執行緒設定為分離狀態以保證該執行緒不會成為殭屍執行緒
2、pthread_detach(id);
3、pthread_attr_setdetachstate
引數
attr:執行緒屬性變數
detachstate
PTHREAD_CREATE_DETACHED:分離
PTHREAD_CREATE_JOINABLE:接合
程式碼
#include <stdio.h>
#include <pthread.h>
void *routine(void *arg)
{
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,
PTHREAD_CREATE_DETACHED);
pthread_t tid;
int i=0;
for(i=0; i<10; i++)
{
pthread_create(&tid, &attr, routine, NULL);
}
pause();
return 0;
}
4、執行緒的退出
void pthread_exit(void *retval);
retval:執行緒退出值
5、 取消一條執行緒
(1)int pthread_cancel(pthread_t thread);
給指定的執行緒傳送一條取消請求
(2)thread:執行緒TID
(3)設定取消狀態
int pthread_setcancelstate(int state, int *oldstate); int pthread_setcanceltype(int type, int *oldtype);
引數
state新的取消狀態
PTHREAD_CANCEL_ENABLE:使能取消請求
PTHREAD_CANCEL_DISABLE:關閉取消請求
oldstate:舊的取消請求
type:新的取消型別
PTHREAD_CANCEL_DEFERRED:延時響應
PTHREAD_CANCEL_ASYNCHRONOUS:立即響應
oldtype:舊的取消型別
5、執行緒資源回收
int pthread_join(pthread_t thread, void **retval);
thread:執行緒TID
retval:儲存執行緒退出值的記憶體的指標
該函式要注意的幾點
如果執行緒退出是沒有退出值,retval可以