本文偏向於實際Unix環境下的進程和線程的操作函數的學習,只涉及部分的理論。
Linux環境下的進程
進程的產生
進程的終止
進程終止將釋放其擁有的資源
線程和進程的主要區別是進程是操作系統資源分配的基本單位,擁有完整的虛擬空間。
線程則除了CPU資源,所有的資源都是要與進程中的其他線程共享資源,不單獨分配資源。
以下函數大部分都要導入
#include <sys/types.h> //包含了大部分的引申的數據類型的庫,如ssize_t,pid_t等
#include <unistd.h> //unix standard library
進程號
getpid()、getppid()函數
前者返回當前進程的id,後者返回當前進程的父進程的id。返回值類型都為pid_t
複製進程fork()
fork()函數產生一個新的進程,除了內存之外,其他的信息都與調用該函數進程共享,包括程序計數器,所以在fork後父進程和子進程同步執行接下來的指令。
在父進程中,fork函數的返回值為子進程的id,在子進程中則返回0
system()方式
system()函數可以在C語言程序中使用外部的shell命令。
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL)
{
return (1); //如果cmdstring为空,返回非零值,一般为1
}
if((pid = fork())<0)
{
status = -1; //fork失败,返回-1
}
else if(pid == 0) //子进程
{
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~
}
else //父进程
{
while(waitpid(pid, &status, 0) < 0)
{
if(errno != EINTR)
{
status = -1; //如果waitpid被信号中断,则返回-1
break;
}
}
}
return status; //如果waitpid成功,则返回子进程的返回状态
}
system()源碼如上。在調用system函數後會產生兩個進程,子進程調用execl函數(execl內部執行系統調用execve())執行shell命令,父進程則阻塞等待子進程的執行。
進程執行exec()函數系列
之前的fork()和system()函數都是新建一個進程來執行操作。exec()族函數則是用新進程代替原來的進程,系統從新進程運行,PID值不變。
初始進程init
所有進程的祖先,original process
管道
管道是由内核管理的一个缓冲区,管道的一端连接一个进程的输出,另一端连接一个进程的输入。
在Linux中,管道的实现没有专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。如下图

管道读写时,管道的写函数通过将字节复制到VFS索引节点指向的物理内存而写入数据,管道的读函数就可以通过复制物理内存中的字节而读出数据。
当写进程向管道中写入时,它利用标准的库函数write(),系统根据库函数传递的文件描述符,可找到该文件的 file 结构。file 结构中指定了用来进行写操作的函数(即写入函数)地址,于是,内核调用该函数完成写操作。写入函数在向内存中写入数据之前,必须首先检查 VFS 索引节点中的信息,同时满足如下条件时,才能进行实际的内存复制工作:
如果同时满足上述条件,写入函数首先锁定内存,然后从写进程的地址空间中复制数据到内存。否则,写入进程就休眠在 VFS 索引节点的等待队列中,接下来,内核将调用调度程序,而调度程序会选择其他进程运行。写入进程实际处于可中断的等待状态,当内存中有足够的空间可以容纳写入数据,或内存被解锁时,读取进程会唤醒写入进程,这时,写入进程将接收到信号。当数据写入内存之后,内存被解锁,而所有休眠在索引节点的读取进程会被唤醒。
管道的读取过程和写入过程类似。但是,进程可以在没有数据或内存被锁定时立即返回错误信息,而不是阻塞该进程,这依赖于文件或管道的打开模式。反之,进程可以休眠在索引节点的等待队列中等待写入进程写入数据。当所有的进程完成了管道操作之后,管道的索引节点被丢弃,而共享数据页也被释放。
管道的种类:
普通管道:有两种限制,一是只能单向传输,二是只能在父子或兄弟进程间进行通信
半雙工管道
只能在父子或兄弟进程间使用,可以双向传输
shell中通過|表示管道,左邊的輸出作為右邊的輸入。
進程創建管道時,需要創建兩個文件描述符來操作管道,一個讀,一個寫。兩個通信的進程中的讀與寫的描述符相對應。在進程A中設置為讀的描述符,在B中設置為寫。
pipe()函數
#include <unistd.h>
int pipe(int filedes[2]);
filedes是一個文件描述符的數組,第1個是讀,第2個是寫。
pipe寫入數據時,寫入的數據小於128K時是非原子的。當大於128K時,緩衝區的數據會被連續地寫入管道,之後阻塞等待讀進程將所有的數據讀完。
命名管道FIFO
文件系統中FIFO以設備特殊文件的形式存在,它在文件系统中有对应的路径,当一个进程以读的方式打开该文件,另一个进程以写的方式打开该文件,那么内核就会在这两个进程间建立管道。
不同的進程可以通過FIFO共享數據
創建FIFO
shell中:mkfifo fifo_name
C語言:
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
FIFO默認狀態下總是處於阻塞狀態,必須使用open()函數來顯式地建立到管道的連接,並且使用O_NONBLOCK標誌
消息隊列

消息隊列時內核空間中的內部鏈錶,通過內核在各個進程間傳遞內容。消息順序地發送到消息隊列中,並以幾種不同的方式從隊列中獲取,每個隊列通過IPC標識符唯一地標識,每個消息隊列中的消息,又構成一個獨立的鏈錶。
每个消息队列都有一个队列头,用结构struct msg_queue来描述。队列头中包含了该消息队列的大量信息,包括消息队列键值、用户ID、组ID、消息队列中消息数目等等,甚至记录了最近对消息队列读写进程的ID。读者可以访问这些信息,也可以设置其中的某些信息。
消息緩衝區結構
#include <linux/msg.h>
struct msgmbuf {
long mtype; //消息類型,用整數表示,可以為某類消息設定一個類型,以區分接收到的不同的消息
char mtext[1]; //消息數據,長度不一定為1
}
对于发送消息来说,首先预置一个msgbuf缓冲区并写入消息类型和内容,调用相应的发送函数即可;对读取消息来说,首先分配这样一个msgbuf缓冲区,然后把消息读入该缓冲区即可。
消息總的大小不能超過8192字節。
ipc_perm
內核將IPC對象的許可權限信息放在ipc_perm類型的結構中。
struct ipc_perm {
key_t key; //用於區分消息隊列
uid_t uid; //用戶的UID
gid_t gid; //用戶組的id
uid_t cuid; //建立者的UID
gid_t cgid;
unsigned short mode; //讀寫控制權限
unsigned short seq; //序列號
};
msqid_ds
消息队列的信息基本上都保存在消息队列头中,因此,可以分配一个类似于消息队列头的结构(struct msqid_ds),来返回消息队列的属性;同样可以设置该数据结构。
每個msqid_ds代表一個系統消息隊列。系統通過struct list_head q_messages來管理消息隊列,一個系統中最優有128個消息隊列。
struct msqid_ds {
?? ?struct ipc_perm msg_perm;
?? ?struct msg *msg_first; ? ? ?/* first message on queue,unused ?*/
?? ?struct msg *msg_last; ? ? ? /* last message in queue,unused */
?? ?__kernel_time_t msg_stime; ?/* last msgsnd time */
?? ?__kernel_time_t msg_rtime; ?/* last msgrcv time */
?? ?__kernel_time_t msg_ctime; ?/* last change time */
?? ?unsigned long ?msg_lcbytes; /* Reuse junk fields for 32 bit */
?? ?unsigned long ?msg_lqbytes; /* ditto */
?? ?unsigned short msg_cbytes; ?/* current number of bytes on queue */
?? ?unsigned short msg_qnum; ? ?/* number of messages in queue */
?? ?unsigned short msg_qbytes; ?/* max number of bytes on queue */
?? ?__kernel_ipc_pid_t msg_lspid; ? /* pid of last msgsnd */
?? ?__kernel_ipc_pid_t msg_lrpid; ? /* last receive pid */
};
消息隊列便通過msg_first和msg_lasr指針來鏈接頭和尾的消息體,消息體定義如下:
/* one msg structure for each message */
struct msg {
struct msg *msg_next; /* next message on queue */
long msg_type;
char *msg_spot; /* message text address */
time_t msg_stime; /* msgsnd time */
short msg_ts; /* message text size */
};
內核中的消息隊列的關係

鍵值構建ftok()函數
由於共享內存、消息隊列、信號量都是通過中間介質來進行通信的,想要唯一一個介質進程通信,需要指定一個id。ftok()函數用於產生這種id。
將路徑名和項目的標識符轉變為一個系統V的IPC鍵值
#include <sys/ipc.h>
key_t ftok(const char* pathname, int proj_id);
pathname是存在且可訪問的文件名,proj_id是隨便指定的一個數,1~255。
ftok()的返回值是根據pathname對應的文件節點信息和proj_id生成的。文件節點結構為
//函数:int stat( const char *file_name, struct stat *buf )
//函数说明:通过文件名filename,获取文件信息,并保存在buf所指的结构体stat中。
//返回值:成功执行返回0,失败返回-1,错误代码存于errno
//struct stat结构体的定义如下:
struct stat {
unsigned long st_dev;//文件的设备编号
unsigned long st_ino;//节点
unsigned short st_mode; //文件的类型和存取的权限
unsigned short st_nlink;//连到该文件的硬连接数目,刚建立的文件值为1
unsigned short st_uid; //用户ID
unsigned short st_gid; //组ID
unsigned long st_rdev;
unsigned long st_size;
unsigned long st_blksize;
unsigned long st_blocks;
unsigned long st_atime;
unsigned long st_atime_nsec;
unsigned long st_mtime;
unsigned long st_mtime_nsec;
unsigned long st_ctime;
unsigned long st_ctime_nsec;
unsigned long __unused4;
unsigned long __unused5;
};
ftok获取的键值是由ftok()函数的第二个参数的后8个bit,st_dev的后两位,st_ino的后四位构成的。
獲得消息隊列id msgget()函數
#include <sys/msg.h>
#include <sys/ipc.h>
int msgget(key_t key, int msgflag);
成功調用返回消息隊列ID
key即為ftok()創建的key
msgflag:
發送消息msgsend()函數
int msgsend(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid:消息隊列id
msgp:指向一個消息緩衝區msgmbuf
msgz:消息的大小,單位字節,不包括4字節的消息類型
msgflg:指定設置為IPC_NOWAIT,如果消息隊列已滿就不繼續寫消息。沒有設置則進程阻塞,直到可以繼續寫消息。
接受消息msgrcv()函數
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz,long msgtyp, int msgflg);
msgtyp表示要從隊列獲取的消息類型,內核將查找隊列中第一個到達的匹配類型的消息。如果傳0表示無論類型,接收最老的消息。
消息控制msgctl()函數
int msgctl(int msqid, int cmd, struct msqid_ds* buf);
msgctl()函數向內核發送一個cmd命令
消息隊列

消息隊列時內核空間中的內部鏈錶,通過內核在各個進程間傳遞內容。消息順序地發送到消息隊列中,並以幾種不同的方式從隊列中獲取,每個隊列通過IPC標識符唯一地標識,每個消息隊列中的消息,又構成一個獨立的鏈錶。
每个消息队列都有一个队列头,用结构struct msg_queue来描述。队列头中包含了该消息队列的大量信息,包括消息队列键值、用户ID、组ID、消息队列中消息数目等等,甚至记录了最近对消息队列读写进程的ID。读者可以访问这些信息,也可以设置其中的某些信息。
消息緩衝區結構
#include <linux/msg.h>
struct msgmbuf {
long mtype; //消息類型,用整數表示,可以為某類消息設定一個類型,以區分接收到的不同的消息
char mtext[1]; //消息數據,長度不一定為1
}
对于发送消息来说,首先预置一个msgbuf缓冲区并写入消息类型和内容,调用相应的发送函数即可;对读取消息来说,首先分配这样一个msgbuf缓冲区,然后把消息读入该缓冲区即可。
消息總的大小不能超過8192字節。
ipc_perm
內核將IPC對象的許可權限信息放在ipc_perm類型的結構中。
struct ipc_perm {
key_t key; //用於區分消息隊列
uid_t uid; //用戶的UID
gid_t gid; //用戶組的id
uid_t cuid; //建立者的UID
gid_t cgid;
unsigned short mode; //讀寫控制權限
unsigned short seq; //序列號
};
msqid_ds
消息队列的信息基本上都保存在消息队列头中,因此,可以分配一个类似于消息队列头的结构(struct msqid_ds),来返回消息队列的属性;同样可以设置该数据结构。
每個msqid_ds代表一個系統消息隊列。系統通過struct list_head q_messages來管理消息隊列,一個系統中最優有128個消息隊列。
struct msqid_ds {
?? ?struct ipc_perm msg_perm;
?? ?struct msg *msg_first; ? ? ?/* first message on queue,unused ?*/
?? ?struct msg *msg_last; ? ? ? /* last message in queue,unused */
?? ?__kernel_time_t msg_stime; ?/* last msgsnd time */
?? ?__kernel_time_t msg_rtime; ?/* last msgrcv time */
?? ?__kernel_time_t msg_ctime; ?/* last change time */
?? ?unsigned long ?msg_lcbytes; /* Reuse junk fields for 32 bit */
?? ?unsigned long ?msg_lqbytes; /* ditto */
?? ?unsigned short msg_cbytes; ?/* current number of bytes on queue */
?? ?unsigned short msg_qnum; ? ?/* number of messages in queue */
?? ?unsigned short msg_qbytes; ?/* max number of bytes on queue */
?? ?__kernel_ipc_pid_t msg_lspid; ? /* pid of last msgsnd */
?? ?__kernel_ipc_pid_t msg_lrpid; ? /* last receive pid */
};
消息隊列便通過msg_first和msg_lasr指針來鏈接頭和尾的消息體,消息體定義如下:
/* one msg structure for each message */
struct msg {
struct msg *msg_next; /* next message on queue */
long msg_type;
char *msg_spot; /* message text address */
time_t msg_stime; /* msgsnd time */
short msg_ts; /* message text size */
};
內核中的消息隊列的關係

鍵值構建ftok()函數
由於共享內存、消息隊列、信號量都是通過中間介質來進行通信的,想要唯一一個介質進程通信,需要指定一個id。ftok()函數用於產生這種id。
將路徑名和項目的標識符轉變為一個系統V的IPC鍵值
#include <sys/ipc.h>
key_t ftok(const char* pathname, int proj_id);
pathname是存在且可訪問的文件名,proj_id是隨便指定的一個數,1~255。
ftok()的返回值是根據pathname對應的文件節點信息和proj_id生成的。文件節點結構為
//函数:int stat( const char *file_name, struct stat *buf )
//函数说明:通过文件名filename,获取文件信息,并保存在buf所指的结构体stat中。
//返回值:成功执行返回0,失败返回-1,错误代码存于errno
//struct stat结构体的定义如下:
struct stat {
unsigned long st_dev;//文件的设备编号
unsigned long st_ino;//节点
unsigned short st_mode; //文件的类型和存取的权限
unsigned short st_nlink;//连到该文件的硬连接数目,刚建立的文件值为1
unsigned short st_uid; //用户ID
unsigned short st_gid; //组ID
unsigned long st_rdev;
unsigned long st_size;
unsigned long st_blksize;
unsigned long st_blocks;
unsigned long st_atime;
unsigned long st_atime_nsec;
unsigned long st_mtime;
unsigned long st_mtime_nsec;
unsigned long st_ctime;
unsigned long st_ctime_nsec;
unsigned long __unused4;
unsigned long __unused5;
};
ftok获取的键值是由ftok()函数的第二个参数的后8个bit,st_dev的后两位,st_ino的后四位构成的。
獲得消息隊列id msgget()函數
#include <sys/msg.h>
#include <sys/ipc.h>
int msgget(key_t key, int msgflag);
成功調用返回消息隊列ID
key即為ftok()創建的key
msgflag:
發送消息msgsend()函數
int msgsend(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid:消息隊列id
msgp:指向一個消息緩衝區msgmbuf
msgz:消息的大小,單位字節,不包括4字節的消息類型
msgflg:指定設置為IPC_NOWAIT,如果消息隊列已滿就不繼續寫消息。沒有設置則進程阻塞,直到可以繼續寫消息。
接受消息msgrcv()函數
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz,long msgtyp, int msgflg);
msgtyp表示要從隊列獲取的消息類型,內核將查找隊列中第一個到達的匹配類型的消息。如果傳0表示無論類型,接收最老的消息。
消息控制msgctl()函數
int msgctl(int msqid, int cmd, struct msqid_ds* buf);
msgctl()函數向內核發送一個cmd命令
信號量
信號量是一種計數器,用來控制多個進程共享的資源的訪問。常常用來預防死鎖。信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。
數據結構
union semun {
int val;
struct semid_ds* buf;
unsigned short* array;
struct seminfo* __buf;
}
semget函数
创建一个新信号量或取得一个已有信号量
int semget(key_t key, int num_sems, int sem_flags)
semget函数成功返回一个相应信号标识符(非零),失败返回-1.
semop函数
改变信号量的值,原型为
int semop(int sem_id, struct sembuf* sem_opa, size_t num_sem_ops);
sem_id是由semget返回的信号量标识符,sembuf结构的定义为
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
共享內存
共享內存時在多個進程之間共享內存區域的一種進程間的通信方式,它是在多個進程之間對內存段進行映射的方式實現內存共享的。這是IPC最快捷的方式,因為共享內存方式的通信沒有中間過程,不需要像其他方式一樣將消息經過中間機制的轉換。
共享內存時直接將某段內存進行映射,多個進程共享的內存是同一塊物理空間,但是邏輯空間的地址不同。
創建共享內存函數shmget()
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
shmget()通過第一個參數比較內核中現有的共享內存的關鍵值,比較之後,打開和訪問操作都依賴於shmflg參數的內容。參數的可選項IPC_CREAT和IPC_EXCL和前面的信號量和消息隊列的參數都一樣。
調用成功後返回新創建的內存段的段標識符。
獲得共享內存地址函數shmat()
利用創建好的共享內存標識符,可以將進程與共享內存綁定。
進程與共享內存解綁shmdt()
但是共享內存解綁後不會立刻變為普通內存,只有當與共享內存連接的進程數量為0時,共享內存才會刪除。
共享內存控制函數shmctl()
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
PC_RMID:删除。(常用 )
IPC_SET:设置 shmid_ds 参数,相当于把共享内存原来的属性值替换为 buf 里的属性值。
IPC_STAT:保存 shmid_ds 参数,把共享内存原来的属性值备份到 buf 里。
SHM_LOCK:锁定共享内存段( 超级用户 )。
SHM_UNLOCK:解锁共享内存段。
SHM_LOCK 用于锁定内存,禁止内存交换。并不代表共享内存被锁定后禁止其它进程访问。其真正的意义是:被锁定的内存不允许被交换到虚拟内存中。这样做的优势在于让共享内存一直处于内存中,从而提高程序性能。
信號
信號機制用於在一個或多個進程之間傳遞異步信號。
查看所有信號kill -l
進程可以屏蔽大多數信號,除了使進程暫停的SIGSTOP和使進程退出的SIGKILL。
進程接收到信號後通常會執行操作系統默認的行為,如果用戶定義了進程的行為則執行用戶設定的行為。
現介紹幾種常用信號
常用信號處理函數
信號截取函數signal()
signal()函數用於截取系統的信號,對此信號掛接用戶自己的處理程序
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
可以看出signal()接收一個信號參數和一個函數指針參數,返回一個沒有返回值的函數指針。
向進程發送信號的kill()和raise()
#include <signal.h>
int kill(pid_t pid, int sig);
int raise(int sig);
kill向進程id為pid的進程發送信號,pid=0時,向所有進程發送信號
raise向當前進程發送信號。
Linux下的多線程遵循POSIX標準,稱為pthread。編寫Linux下的多線程需要包含頭文件pthread.h,生成可執行文件時需要鏈接庫libptherad.a或者libpthread.so。
線程創建函數pthread_create()
int pthread_create(pthread_t* thread, pthread_attr* attr, void* (*start_routine)(void*),void* arg);
thread為進程標識符,attr表示屬性,進程用來設置線程的優先級。start_routine為分配進程需要執行的函數程序,
原文:https://www.cnblogs.com/lunar-ubuntu/p/12910140.html