#include
#include
pid_t fork(void);
总结一下规律,N次循环创建的子进程数为 :
1 + 2 + 4 + …… + 2^N-1
个
守护进程要fork两次的原因:
第一次fork:这里第一次fork的作用就是让shell认为这条命令已经终止,不用挂在终端输入上;再一个是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组组长,如果不fork子进程,那么此时的父进程是进程组组长,无法调用setsid。所以到这里子进程便成为了一个新会话组的组长。
第二次fork:第2次fork不是必须的。也看到很多开源服务没有fork第二次。fork第二次主要目的是。防止进程再次打开一个控制终端。因为打开一个控制终端的前台条件是该进程必须是会话组长。再fork一次,子进程ID != sid(sid是进程父进程的sid)。所以也无法打开新的控制终端。
daemon目的就是防止终端产生的一些信号让进程退出。上面函数并没有直接调用signal函数去处理它。而是间接通过fork和setsid函数使用更少代码优雅处理。而被有些人误以为是防止父进程没有正常退出而导致的僵死进程的原因需要这样处理。
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程控制块PCB仍然保存在系统中。这种进程称之为僵死进程。
守护进程已经完全脱离终端控制台了,而后台程序并未完全脱离终端(在终端未关闭前还是会往终端输出结果);守护进程在关闭终端控制台时不会受影响,而后台程序会随用户退出而停止,需要在以nohup command &格式运行才能避免影响;守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行,这些都没改变;
ps ajx|more
-a 表示显示由其他用户所拥有的进程的状态
-x 显示没有控制终端的进程状态
-j 显示与作业有关的信息:会话ID、进程组ID等
/**
* #include
*
* @brief pipe 匿名管道:
* 其本质是一个伪文件(实为内核缓冲区),由两个文件描述符引用,一个表示读端,一个表示写端
* 规定数据从管道的写端流入管道,从读端流出,内核使用环形队列机制,借助内核缓冲区(4k)实现
* 数据一旦被读走,便不在管道中存在,不可反复读取。
* @param pipefd:fd[0] → r; fd[1] → w
*
* // Standard file descriptors.
* #define STDIN_FILENO 0 // Standard input.
* #define STDOUT_FILENO 1 // Standard output.
* #define STDERR_FILENO 2 // Standard error output.
*
* @return 成功:0;失败:-1,设置errno
*
* int pipe(int pipefd[2]);
*/
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char* argv[])
{
int pipefd[2];
pid_t cpid;
char buf;
char writebuf[128] = "wang";
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
printf("pipe-->read fd:pipefd[0]=%d,write fd:pipefd[1]=%d\n",pipefd[0],pipefd[1]);
cpid = fork();
if (cpid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) /* Child reads from pipe */
{
close(pipefd[1]); /* Close unused write end */
while (read(pipefd[0], &buf, 1) > 0)
write(STDOUT_FILENO, &buf, 1);
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else
{
/* Parent writes argv[1] to pipe */
close(pipefd[0]); /* Close unused read end */
write(pipefd[1], writebuf, strlen(writebuf));
close(pipefd[1]); /* Reader will see EOF */
wait(NULL); /* Wait for child */
exit(EXIT_SUCCESS);
}
}
/**
* #include
* #include
*
* @brief
* @param pathname:创建的有名管道的全路径名
* @param mode:指明其存取权限
*
* @return 成功:0;失败:-1,设置errno
*
* int mkfifo(const char *pathname, mode_t mode);
*/
#include
#include
#include
#include
int main ( )
{
int res = mkfifo( "/tmp/my_fifo", 0777);
if (res == 0)
{
printf("FIFO created\n");
}
return 0;
}
所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的地址空间里去。所有进程都能访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会即时被有访问同一段共享内存的其他进程看到,由于共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。以下讨论的为System-V 规范下的共享内存相关函数:
/**
* #include
* #include
*
* @brief shmget:创建共享内存数据空间,并返回标识
* @param key :为共享内存段命名,可以使用以下等方式得到:
* 1)IPC_PRIVATE:此时 key 值为 0,类似匿名管道
* #define IPC_PRIVATE ((__key_t) 0)
* 2) 通过 ftok() 函数创建,此时第三个参数需要添加 IPC_CREAT 属性
* key 通过计算得到,有自己特定的名字,类似有名管道
* @param size :缓冲区的大小,以字节为单位
* 1) 注意,当创建一个新的共享内存区时,size 的值必须大于 0 ;
* 如果是访问一个已经存在的内存共享区,则置 size 为 0
* 2) 重复执行如果 key 相同,size 比上次大,报错,比上次小,保持上次大小
* @param shmflg :操作选项:
* 1)IPC_CREATE:
* 创建一个新的共享内存,系统会与其他共享内存区的 key 比较,存在相同的 key ,
* 说明共享内存区已存在,返回标识符,否则新建一个共享内存区并返回其标识符。
* 2)IPC_EXCL:该宏必须和 IPC_CREATE 一起使用,否则没意义。
* 当 shmflg 取 IPC_CREATE | IPC_EXCL 时,
* 表示如果发现内存区已经存在则返回 -1,错误代码为 EEXIST
*
* @return 成功返回共享内存块的描述符标志,错误返回 -1,并设置 errno 号
*
* int shmget(key_t key, size_t size, int shmflg);
*/
/**
* #include
* #include
*
* @brief ftok:把一个文件名或者文件夹转换成 System-V 的 IPC key
* @param pathname :pathname 一定要是物理存在的
* @param proj_id:要求为非 0 的至少 8 位范围内的值
* @return 成功返回 key,错误返回 -1,并设置 errno 号
*
* key_t ftok(const char *pathname, int proj_id);
*/
/**
* #include
* #include
*
* @brief shmat :
* 当首次创建共享内存段时,它并不能被任何进程所访问。为了使共享内存区可以被访问,
* 则必须通过 shmat 函数将其附加( attach )到自己的进程空间中,这样进程就与共享内存建立了连接。
* @param shmid : shmget() 的返回值
* @param shmaddr:表示的是物理内存空间映射到进程的虚拟内存空间时,虚拟内存空间中该块内存的起始地址
* 1) NULL:系统分配一个页对齐的可用数据段
* 2)不为空并且 shmflg 标志设置为 SHM_RND ,将向下取整对齐进行分配空间
* 3)其他情况都将进行页对齐分配
* @param shmflg:存取权限标志;如果为 0 ,则不设置任何限制权限
* Flags for `shmat'.
* #define SHM_RDONLY 010000 // attach read-only else read-write
* #define SHM_RND 020000 // round attach address to SHMLBA
* #define SHM_REMAP 040000 // take-over region on attach
* #define SHM_EXEC 0100000 // execution access
* 1)SHM_RND:空间对齐并向下取整
* 2)SHM_EXEC:可执行权限
* 3) SHM_RDONLY:只有读取权限
* 4) SHM_REMAP:
* @return 成功后返回一个指向共享内存区的指针,使用该指针就可以访问共享内存区
* 错误返回 (void*)-1,并返回错误号
*
* void *shmat(int shmid, const void *shmaddr, int shmflg);
*/
/**
* #include
* #include
*
* @brief shmdt: 回收 shmat 函数返回值分配的空间
* @param shmaddr:shmat 函数的返回值
* @return
*
* int shmdt(const void *shmaddr);
*/
/**
* #include
* #include
*
* @brief shmctl:共享内存操作控制函数
* @param shmid
* @param cmd:
* 1)IPC_STAT:得到共享内存的状态,把共享内存的 shmid_ds 结构复制到buf中
* 2)IPC_SET:改变共享内存的状态,把 buf 所指的 shmid_ds 结构中的uid、gid、mode
* 复制到共享内存的 shmid_ds 结构内
* 3)IPC_RMID:删除这片共享内存
* 4)IPC_INFO SHM_INFO ...
* @param buf
* @return 错误返回 -1,并返回错误号
*
* int shmctl(int shmid, int cmd, struct shmid_ds *buf);
*/
#include
#include //system 函数
#include
#include
#include
#include
//#define USE_IPC_PRIVATE
#define USE_FTOK
int main()
{
int shmid = -1;
int key = -1;
size_t size = 128;
///create///
#if defined(USE_IPC_PRIVATE)
key = IPC_PRIVATE;
shmid = shmget(key,size,0777);
#elif defined(USE_FTOK)
key = ftok("./LinuxIPC",'a');
if(key < 0)
{
printf("create key failed..\n");
return -1;
}
// shmid = shmget(key,size,IPC_CREAT|IPC_EXCL|0777);//添加 IPC_EXCL 已存在会报错
shmid = shmget(key,size,IPC_CREAT|0777);//已存在size不超过上次返回
#endif
if(shmid < 0)
{
printf("create shmget failed..\n");
return -1;
}
printf("create shmget success,key=%d,shmid=%d\n",key,shmid);
/shmat///
char *p = NULL;
p = (char*)shmat(shmid, NULL, 0);
if(p == (void*)-1)
{
printf("first create shmat failed.errno=%d\n",errno);
}
printf("first create shmat success:addr=%p\n",p);
// 一个进程是可以对同一个共享内存多次 shmat进行挂载的,物理内存是指向同一块,
//如果shmaddr为NULL,则每次返回的线性地址空间都不同
//和第一次的不一致,指向这块共享内存的引用计数会增加。也就是进程多块线性空间会指向同一块物理地址
//如果之前挂载过这块共享内存的进程的线性地址没有被shmdt掉,就会一直消耗进程的虚拟内存空间
p = (char*)shmat(shmid, NULL, 0);
if(p == (void*)-1)
{
printf("second create shmat failed.errno=%d\n",errno);
return -1;
}
printf("second create shmat success:addr=%p\n",p);
/shmdt-shmctl///
int ret = shmdt(p);
if(ret == -1)
{
printf(" shmdt failed.errno=%d\n",errno);
return -1;
}
printf(" shmdt success.\n");
ret = shmctl(shmid, IPC_RMID, 0);
if(ret == -1)
{
printf(" shmctl failed.errno=%d\n",errno);
return -1;
}
printf(" shmctl success.\n");
//执行指令查看共享内存
system("ipcs -m");
return 0;
}
create shmget success,key=1627489634,shmid=524327
first create shmat success:addr=0x7fb204cac000
second create shmat success:addr=0x7fb204cab000
shmdt success.
shmctl success.
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 98304 brody 600 237568 2 dest
0x00000000 196610 brody 600 6443008 2 dest
0x00000000 196611 brody 600 6443008 2 dest
0x00000000 32 brody 600 524288 2 dest
0x00000000 33 brody 600 524288 2 dest
0x00000000 262178 brody 600 16777216 2 dest
0x00000000 524324 brody 600 524288 2 dest
0x00000000 38 brody 600 542416 2 dest
0x00000000 524327 brody 777 128 1 dest
在一个linux服务器上,共享内存的总体大小是有限制的,这个大小通过SHMMAX参数来定义(以字节为单位),您可以通过执行以下命令来确定 SHMMAX 的值:
# cat /proc/sys/kernel/shmmax
如果机器上创建的共享内存的总共大小超出了这个限制,在程序中使用标准错误perror可能会出现以下的信息:
unable to attach to shared memory
这个内核参数用于设置系统范围内共享内存段的最大数量。该参数的默认值是 4096
# cat /proc/sys/kernel/shmmni
该参数控制着系统一次可以使用的共享内存总量(以页为单位)。简言之,该参数的值始终应该至少为:
# cat /proc/sys/kernel/shmall
system V 消息队列
/**
* #include
* #include
* #include
*
* @brief msgget:创建消息队列
* @param key:同 shmget 中的 key
* @param msgflg:
* 1)IPC_CREATE:
* 创建一个新的共享内存,系统会与其他共享内存区的 key 比较,存在相同的 key ,
* 说明共享内存区已存在,返回标识符,否则新建一个共享内存区并返回其标识符。
* 2)IPC_EXCL:该宏必须和 IPC_CREATE 一起使用,否则没意义。
* 当 shmflg 取 IPC_CREATE | IPC_EXCL 时,
* 表示如果发现内存区已经存在则返回 -1,错误代码为 EEXIST
*
* @return 成功返回共享内存块的描述符标志,错误返回 -1,并设置 errno 号
*
* int msgget(key_t key, int msgflg);
*/
/**
* #include
* #include
* #include
*
* @brief msgsnd:往消息队列发送消息
* @param msgid:msgget 函数返回值
* @param msgp:重新定义msgbuf结构提并传入
*
* struct msgbuf {
* long mtype; // message type, must be > 0,接收方根据此来接收
* char mtext[1]; // message data
* };
* @param msgsz:消息体大小,去掉结构体中类型的空间大小
* @param msgflg:
* 1)0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列或者消息队列被删除
* 2)IPC_NOWAIT:当消息队列满了,msgsnd函数将不会等待,会立即出错返回EAGAIN
*
* @return: 发送成功返回0,失败返回-1
*
* int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);
*/
/**
* #include
* #include
* #include
*
* @brief msgrcv:从消息队列接受数据,接收过的数据将不会保存在队列中
* @param msqid
* @param msgp:消息类型,需要和 msgsnd 发送的 msgbuf 的 mtype 保持相同
* struct msgbuf {
* long mtype; // message type, must be > 0,接收方根据此来接收
* char mtext[1]; // message data
* };
* @param msgsz
* @param msgtyp:
* 1)msgtyp == 0 :返回队列中的第一个消息
* 2) msgtyp > 0 :返回队列中消息类型为 msgtyp 的第一个消息
* 3) msgtyp < 0 :返回队列中消息类型值小于等于 msgtyp 绝对值的消息,
* 如果这种消息有若干个,则取类型值最小的消息
* @param msgflg:
* 1)0:阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待
* 2)IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG
* 3) IPC_EXCEPT:与 msgtype 配合使用返回队列中第一个类型不为 msgtype 的消息
* @return 成功返回消息数据部分的长度;错误返回-1
*
* ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
*/
/**
* #include
* #include
* #include
*
* @brief msgctl
* @param msqid
* @param cmd:
* 1)IPC_STAT:把内核消息队列的 msqid_ds 结构复制到buf中
* 2)IPC_SET:改变消息队列的状态,把 buf 所指的 msqid_ds 结构中的 msg_qbytes,
* msg_perm.uid, msg_perm.gid,msg_perm.mode复制到 msqid_ds 结构内
* 3)IPC_RMID:删除消息队列
* 4)IPC_INFO SHM_INFO ...
* @param buf
*
* 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)
* };
*
* @return:成功返回0;错误返回-1
*
* int msgctl(int msqid, int cmd, struct msqid_ds *buf);
*/
#include
#include
#include
#include
#include
#define BUFFERSIZE 128
struct msgbuf
{
long mtype;
char mtext[BUFFERSIZE];
};
//#define USE_IPC_PRIVATE
#define USE_FTOK
int main()
{
int msgid = -1;
int key = -1;
///create///
#if defined(USE_IPC_PRIVATE)
key = IPC_PRIVATE;
//每执行一次,创建一个新的消息队列
msgid = msgget(key,0777);
#elif defined(USE_FTOK)
key = ftok("/tmp",'a');
if(key < 0)
{
printf("create key failed..\n");
return -1;
}
// msgid = msgget(key,IPC_CREAT|IPC_EXCL|0777);//添加 IPC_EXCL 已存在会报错
//因为 key 相同,则多次执行已存在不会创建新的消息队列
msgid = msgget(key,IPC_CREAT|0777);
#endif
if(msgid < 0)
{
printf("create msgget failed..\n");
return -1;
}
printf("create msgget success,key=%d,msgid=%d\n",key,msgid);
///send to msg queue///
struct msgbuf sendbuffer;
memset(&sendbuffer,0,sizeof(struct msgbuf));
sendbuffer.mtype = 100;
printf("please input data:");
fgets(sendbuffer.mtext,BUFFERSIZE,stdin);
printf("input data=%s,len=%d\n",sendbuffer.mtext,strlen(sendbuffer.mtext));
//去掉 fgets 读入的回车符
int ret = msgsnd(msgid, &sendbuffer, strlen(sendbuffer.mtext)-1, 0);
if(ret == -1)
{
printf("magsnd failed..\n");
return -1;
}
//成功返回 0
printf("magsnd data ret=%d\n",ret);
///recv from msg queue///
struct msgbuf recvbuffer;
memset(&recvbuffer,0,sizeof(struct msgbuf));
//recvbuffer.mtype = 100;
ret = msgrcv(msgid, (void*)&recvbuffer, BUFFERSIZE, 100, 0);
if(ret == -1)
{
printf("magrcv failed..\n");
return -1;
}
//成功返回 0
printf("magrcv data=%s\n",recvbuffer.mtext);
system("ipcs q");
return 0;
}
Systm V | POSIX |
---|---|
semctl() | sem_getvalue() |
semget() | sem_post() |
semop() | sem_timedwait() |
/ | sem_trywait() |
/ | sem_wait() |
/ | sem_destroy() |
/ | sem_init() |
/ | sem_close() |
/ | sem_open() |
/ | sem_unlink() |
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
- P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
- V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
/*
* Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved.
*
* This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SEMAPHORE_H_
#define SEMAPHORE_H_
/*
* 目前发现信号量在32位的系统上有问题,
* 休眠的线程无法被正常唤醒,先禁用之
#if defined(__linux__)
#include
#define HAVE_SEM
#endif //HAVE_SEM
*/
#include
#include
namespace toolkit {
class semaphore {
public:
explicit semaphore(size_t initial = 0) {
#if defined(HAVE_SEM)
sem_init(&_sem, 0, initial);
#else
_count = 0;
#endif
}
~semaphore() {
#if defined(HAVE_SEM)
sem_destroy(&_sem);
#endif
}
void post(size_t n = 1) {
#if defined(HAVE_SEM)
while (n--) {
sem_post(&_sem);
}
#else
std::unique_lock lock(_mutex);
_count += n;
if (n == 1) {
_condition.notify_one();
} else {
_condition.notify_all();
}
#endif
}
void wait() {
#if defined(HAVE_SEM)
sem_wait(&_sem);
#else
std::unique_lock lock(_mutex);
while (_count == 0) {
_condition.wait(lock);
}
--_count;
#endif
}
private:
#if defined(HAVE_SEM)
sem_t _sem;
#else
size_t _count;
std::recursive_mutex _mutex;
std::condition_variable_any _condition;
#endif
};
} /* namespace toolkit */
#endif /* SEMAPHORE_H_ */
/*
* Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved.
*
* This file is part of ZLToolKit(https://github.com/xia-chu/ZLToolKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef TASKQUEUE_H_
#define TASKQUEUE_H_
#include
#include
#include
#include
#include
#include "Util/List.h"
#include "semaphore.h"
using namespace std;
namespace toolkit {
//实现了一个基于函数对象的任务列队,该列队是线程安全的,任务列队任务数由信号量控制
template
class TaskQueue {
public:
//打入任务至列队
template
void push_task(C &&task_func) {
{
lock_guard lock(_mutex);
_queue.emplace_back(std::forward(task_func));
}
_sem.post();
}
template
void push_task_first(C &&task_func) {
{
lock_guard lock(_mutex);
_queue.emplace_front(std::forward(task_func));
}
_sem.post();
}
//清空任务列队
void push_exit(size_t n) {
_sem.post(n);
}
//从列队获取一个任务,由执行线程执行
bool get_task(T &tsk) {
_sem.wait();
lock_guard lock(_mutex);
if (_queue.empty()) {
return false;
}
tsk = std::move(_queue.front());
_queue.pop_front();
return true;
}
size_t size() const {
lock_guard lock(_mutex);
return _queue.size();
}
private:
List _queue;
mutable mutex _mutex;
semaphore _sem;
};
} /* namespace toolkit */
#endif /* TASKQUEUE_H_ */