进程
一 概念
1 定义
进程是一个独立的可调度的任务
进程是一个程序执行一次的过程
进程是程序执行和资源管理的最小单位
2 与程序区别
1 程序是一个可执行的二进制文件,静态
2 进程是一个程序执行过程,动态
3 进程部分数据从程序中来,比如代码段、用户数据段。但是进程中的堆、栈、pc计数器确实程序所没有的。
进程包含三个段:数据段 存放的是全局变量,常数以及动态数据分配的数据空间。
正文段 放的是程序中的代码段
堆栈段 放的是函数的返回地址,函数的参数以及程序中的局部变量。
4 使用进程号标识一个进程,进程号是一个进程的唯一标识,操作系统通过此标识找到进程,getpid获取当前进程ID, getppid获取当前进程的父进程ID
pstree查看进程树,注意init进程是所有进程的祖先进程。init进程号为1 pid_t getpid(void);
pid_t getppid(void);
5 进程类型:交互进程。批处理进程;重点守护进程
6 运行状态:运行态、等待态(可中断,不可中断)、停止态(strl+z)、停止态或僵尸态
7 进程的执行模式
用户模式
内核模式
8 常用命令top ps kill
进程具有并发性,动态性,交互性和独立性。
并发性:指的是系统中多个进程可以同事并发执行,相互之间不受干扰。
动态性:指的是进程都有完整的声明周期,而且在进程周期内,进程的状态是不断变化的,另外进程具有动态的地址空间(代码,数据和进程控制块)。
交互性:指的是进程在执行过程中可能会与其他进程发生直接或间接的通信,如进程同步和互斥,需要添加一定的进程处理机制。
独立性:指的是进程是一个相对完整的资源分配和调度的基本单位,各个进程的地址空间是相互独立的,只有采用某些特定的通信机制才能实现进程间的通信。
Linux系统中主要包括以下几种类型的进程
(1)交互式进程:这类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入之后,
这类进程能够立刻响应,典型的交互式进程有shell命令进程,文本编辑器和图形应用程序运行等。
(2)批处理进程:这类进程不必与用户进行交互,因此通常在后台运行,因为这类进程通常不必很快的响应,
因此往往不会优先调度,典型的批处理进程是编译器的编译操作,数据库搜索引擎。
(3)守护进程:这类进程一直在后台运行,和任何终端都不关联,通常启动系统时开始执行,系统关闭时才结束,
很多系统进程都是以守护进程的形式存在。
Linux下的进程结构
进程不但包括程序的指令和数据,而且包括程序计数器和处理器的所有寄存器以及存储临时数据的进程堆栈。
内核将所有进程存放在双向循环链表中,链表的每一项都是task_struct,称为进程控制块的结构,
该结构包含了一个与进程相关的所有信息,他能完整的描述一个进程,如进程的状态,进程的基本信息,
进程标识符,内存相关信息,父进程相关信息,与进程相关的终端信息,当前工作目录,打开的文件信息,所接收的信号信息
进程状态: 1.运行状态
2.可中断的阻塞状态
3.不可中断的阻塞状态
4.暂停状态
5.僵死状态
6.消亡状态
进程标识符:Linux内核通过唯一的进程标识符PID来标识每个进程。
Linux进程的创建:1.fork()通过复制当前进程创建一个子进程,子进程与父进程的区别仅仅在于不同的PID,
PPID和某些资源及统计量。exec函数族负责读取可执行文件并将其载入地址空间开始运行。
进程的内存结构:Linux操作系统采用虚拟内存管理技术,使得每个进程都有独立的地址空间,
该空间是大小为4GB的线性虚拟空间,用户所看到的和接触到的都是该虚拟地址,
无法看到实际的物理内存地址,利用这种虚拟地址不但更安全,而且用户程序可以使用比实际物理
内存更大的地址空间。它被分为两部分-用户空间与内核空间,用户空间地址是0-3GB,
内核空间地址是3-4GB,用户进程通常情况下只能访问用户空间的虚拟地址,
不能访问内核空间虚拟地址,只有用户进程使用系统调用时才可以访问到内核空间,
每当进程切换,用户空间就会跟着变化,二内核空间由内核负责映射,他不会跟着进程改变,
是固定的。每个进程的用户空间是完全独立的,互不相干的。
用户空间包括以下几个功能区:
1.只读段。包含程序代码,和只读数据。
2.数据段。存放的是全局变量和静态变量,其中可读可写数据段存放已初始化的全局变量和静态变量。
BSS数据段存放未初始化的全局变量和静态变量。
3.栈。由系统自动分配释放,存放函数的参数值,局部变量的值,返回地址等。
4.堆。存放动态分配的数据,一般由程序员动态分配和释放,若程序员不释放,程序结束时可能由操作系统回收。
5.共享的内存映射区域。这是Linux动态连接器和其他共享库代码的映射区域。
二 系统调用
1 fork
fork()函数用于从已存在的进程中创建一个新进程,新进程称为子进程,而原进程称为父进程。
1 fork函数,创建一个子进程,子进程拷贝父进程数据
两个进程在调度时先后顺序不确定。
2 重点:返回值,子进程拷贝了父进程数据,在执行时从fork调用后开始执行,子进程fork返回值为0
父进程中fork返回子进程pid。
exec函数 : 提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,
并用他来取代原调用进程的数据段,代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其余全被替换了。
可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
何时调用exec函数
1.当进程认为自己不能为系统和用户做出任何贡献时就可以调用exec函数。
2.如果一个进程想执行另一个程序,那么它就可以调用fork()函数新建一个进程,
然后调用exec函数族中的任意一个函数,这样看起来就像通过执行应用程序而产生一个新进程。
2 vfork
创建进程同fork,但只创建进程不拷贝父进程数据,需要改变父进程数据时才拷贝(何时拷贝),写时拷贝
3 exec函数族
一共6个函数,但目的只有一个:执行一个可执行文件,并传递参数,参数传递方式有两种:
一种是逐个列举方式,另一种是将所有的参数通过指针数组传递。
参数分为命令行参数和环境变量
命令行参数传递方式
1 l(list),以逗号分隔,一个一个列出命令行参数,例如 “aaa”,"bbb","ccc",NULL
2 v(vector),字符串数组,以NULL结尾
环境变量传递
v只有一种方式,字符串数组,以NULL结尾。
exec函数族中,l--list v--vector e--环境变量 p--PATH环境变量中查找可执行程序,只给出文件名即可
4 exit和_exit,入参进程退出状态,int类型
void(int status)(status是一个整形的参数,可以利用这个参数传递进程结束时的状态,
一般来说,0表示正常结束,其他的值表示非正常结束)
注意exit退出时清理IO缓冲区(输出缓冲区的内容),_exit退出时不清理IO缓冲区(不输出缓冲区内容)
5 wait和waitpid
wait用于等待子进程退出并获取状态,如子进程未退出,则阻塞在wait处
waitpid用于等待指定的子进程退出,并获取状态,退出状态需要有exit或_exit指定
当设置为WNOHANG时,不阻塞,如果子进程未退出,则返回为0,子进程退出返回子进程ID
退出状态需要由WEXITSTATUS获取。
三 孤儿进程和僵尸进程
孤儿进程:父进程先于子进程结束,此时子进程的父进程为init进程,孤儿进程退出有init进程处理,无危害
僵尸进程:父进程未处理子进程退出状态,此时子进程所占用的空间已经释放但仍占用一个tesk_struct结构体
僵尸进程在开发过程中一定要避免,因为task_struct也会占用一定的系统资源。
避免方式 1 可以使用wait或waitpid方式避免
2 忽略子进程退出信号 signal(SIGCHLD, SIG_IGN)
3 等待线程结束和线程信息返回
pthread_join
参数1 等待的线程
参数2 线程的结束信息,注意参数为void *类型,意味着可以指向任意类型的一块内存,也就是说与
创建线程时候入参一样可以是任意类型。
ptherad_exit
参数1 void *类型,其返回数据由pthread_join获取
4 可重入函数和线程安全函数
从数据独立性角度考虑,当线程函数中存在全局变量或静态变量时入不加锁控制,
则在多线程访问时会引起数据错乱,此时线程函数为不可重入函数,
使用malloc或局部变量保证数据独立可以使函数变成可重入函数。
四 互斥
1 目的是保存临界资源,资源有可能是一段代码的连续执行,也有可能是全局变量,
但互斥的目的是保证同一时刻只有且只能有一个操作在修改临界资源
2 线程互斥锁
pthread_mutex_init 初始化锁
pthread_mutes_lock 加锁
pthread_mutex_unlock 解锁
五 同步:强调的是顺序
1 信号量 P V 操作
P 信号量值减1 , V 信号量值加一
在sem_wait未申请到信号量资源时,阻塞,一直等到申请到信号量资源
2 相关函数
sem_init 初始化信号量
参数1 信号量
参数2 0:用于线程,1 用于进程
参数3 信号量初始值
sem_wait p操作
sem_post v操作
*******注意在sem_wait申请资源后之后,sem_post释放资源之前存在return语句情况下需要在return前释放资源
*******注意在加锁后解锁前,有return语句情况下,需要在return之前解锁
- #include
- #include
- #include
- #include
- void *Func(void *arg)
- {
- char *pTmp = (char *)arg;
- #ifndef MALLOC_MEMORY
- static char s_arr[11] = {0};
- #else
- printf("malloc\r\n");
- char *s_arr = (char *) malloc (11);
- if (NULL == s_arr)
- {
- return (void *)NULL;
- }
- memset(s_arr, 0, 11);
- #endif
- int i = 0;
- for(; i < 10; i++)
- {
- s_arr[i] = pTmp[i];
- //usleep(2000);
- //sleep(1);
- }
- printf("%s\r\n", s_arr);
- #ifdef MALLOC_MEMORY
- free(s_arr);
- s_arr = NULL;
- #endif
- }
- int main()
- {
- pthread_t tID1;
- pthread_t tID2;
- char arr1[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
- char arr2[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};
- if (0 != pthread_create(&tID1, NULL, Func, (void *)arr1))
- {
- return -1;
- }
- if (0 != pthread_create(&tID2, NULL, Func, (void *)arr2))
- {
- return -1;
- }
- pthread_join(tID1, NULL);
- pthread_join(tID2, NULL);
- return 0;
- }
线程锁
引入互斥锁的目的是用来保证共享数据的操作完整性,主要用来保护临界资源
- #include
- #include
- pthread_mutex_t g_mutex;
- void *Func(void *arg)
- {
- pthread_mutex_lock(&g_mutex);
- char *pTmp = (char *)arg;
- if (NULL == pTmp)
- {
- pthread_mutex_unlock(&g_mutex);
- return (void *)NULL;
- }
- static char s_arr[11] = {0};
- int i = 0;
- for(; i < 10; i++)
- {
- s_arr[i] = pTmp[i];
- //usleep(2000);
- sleep(1);
- }
- printf("%s\r\n", s_arr);
- pthread_mutex_unlock(&g_mutex);
- }
- int main()
- {
- pthread_t tID1;
- pthread_t tID2;
- char arr1[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
- char arr2[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};
- if (0 != pthread_mutex_init(&g_mutex, NULL))
- {
- return -1;
- }
- if (0 != pthread_create(&tID1, NULL, Func, (void *)arr1))
- {
- return -1;
- }
- if (0 != pthread_create(&tID2, NULL, Func, (void *)arr2))
- {
- return -1;
- }
- pthread_join(tID1, NULL);
- pthread_join(tID2, NULL);
- return 0;
- }
线程间同步
多线程共享同一个进程的地址空间
优点 : 线程间很容易通信,通过全局变量实现数据共享和交换
缺点 : 多个线程同时访问共享对象时需要引入同步和互斥机制
同步是指多个任务按照规定的顺序相互配合完成一件事情
信号量代表某一类资源,其值表示系统中该资源的数量,他是一个受保护的变量,只能通过三种操作来访问
- 初始化
- P操作(申请资源)
- V操作(释放资源)
posix 中定义了两类信号量
常用函数
int sem_init(sem_t *sem,int pshared,unsigned int value);
sem:初始化的信号量
pshared:信号量共享的范围(0是线程间使用 非0是进程间使用)
value : 信号量初值
成功返回:0
失败返回:-1
int sem_wait(sem_t *sem); //p操作
int sem_post(sem_t *sem); //V操作
- #include
- #include
- #include
- sem_t g_sem1;
- sem_t g_sem2;
- void *Func1(void *arg)
- {
- int i = 10;
- while(i--)
- {
- sem_wait(&g_sem1);
- printf("hello\r\n");
- sleep(1);
- sem_post(&g_sem2);
- }
- }
- void *Func2(void *arg)
- {
- int i = 10;
- while(i--)
- {
- sem_wait(&g_sem2);
- printf("world\r\n");
- sleep(1);
- sem_post(&g_sem1);
- }
- }
- int main()
- {
- pthread_t tId1;
- pthread_t tId2;
- if (0 != sem_init(&g_sem1, 0, 1) || 0 != sem_init(&g_sem2, 0, 0))
- {
- return -1;
- }
- if (0 != pthread_create(&tId1, NULL, Func1, NULL))
- {
- return -1;
- }
- if (0 != pthread_create(&tId2, NULL, Func2, NULL))
- {
- return -1;
- }
- pthread_join(tId1, NULL);
- pthread_join(tId2, NULL);
- return 0;
- }
- #include
- #include
- #include
- #include
- #include
- int main()
- {
- pid_t pid, ret;
- if ((pid == fork()) < 0)
- {
- printf("error fork\r\n");
- return -1;
- }
- else if (pid == 0) //子进程
- {
- //子进程暂停5秒
- sleep(5);
- //子进程正常退出
- exit(0);
- }
- else //父进程
- {
- //循环测试子进程是否退出
- do
- {
- //调用waitpid ,且父进程不阻塞
- //ret = waitpid(pid,NULL,WNOHANG); //WNOHANG:若指定的子进程没有结束,则waitpid不阻塞而立即返回,此时返回值为零
- //如果改为ret = waitpid(pid, NULL, 0);父进程会一直阻塞
- ret = waitpid(pid, NULL, 0);
- //若子进程未退出,则父进程暂停1s
- //若子进程退出,waitpid返回子进程号,若没有子进程退出,waitpid返回0
- if (ret == 0)
- {
- printf("The child process has not exited,pid = %d\n", getpid());
- sleep(1);
- }
- } while (ret == 0);
- //若发现子进程退出, 打印出相应信息
- if (pid == ret)
- {
- printf("child process exited,pid = %d\n", getpid());
- }
- else
- {
- printf("some error occured.\n");
- }
- }
- }
一、进程间的通信方式
# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。
进程的亲缘关系通常是指父子进程关系。
# 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量(semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。
它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。
因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。
消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 (sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,是最有效的进程间通信方式。
这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,
它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,
如信号两,配合使用,来实现进程间的同步和通信。
# 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,
它可用于不同及其间的进程通信。
pipe
管道是基于文件描述符的通信方式。当一个管道建立时,他会创建两个文件描述符fd[0]和fd[1]. 其中fd【0】用于读管道,而fd[1]用于写管道。
管道包括有名管道和无名管道
无名管道只能用于具有亲缘关系的进程之间的通信(父子之间)
是一个单工的通信模式,具有固定的读端和写端
管道可以看成是一种特殊的文件,对于他的读写也可以使用普通的read,write函数
他不属于任何文件系统, 并且只存在于内存中。
有名管道
可以使互不相关的两个进程实现彼此通信
该管道可以通过路径名来指出, 并且在文件系统中是可见的,在建立了管道之后,两个进程就可以把它当做普通文件一样进行读写。
FIFO严格的遵守先进先出规则,不支持如lssek()操作
管道
通讯领域概念
半双工:传输方向双向,但同一时刻只有一个方向
双工:传输方向双向,可以双向同时传输
单工:传输方向单向
无名管道
具有亲缘关系的进程
读写端固定,fd[0] 读, fd[1] 写
pipe read write close
入参 一个2个成员的描述符数组
返回值 0成功, -1失败
半双工,但经常安装单工使用,原因:两个进程之间很难保证在同一时刻或者读或者写。
有名管道
单工
无名管道只能用于具有亲缘关系的进程之间,这就限制了无名管道的使用范围
有名管道可以使互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见
进程通过文件IO来操作有名管道
有名管道遵循先进先出规则
不支持如lseek() 操作
向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。
如果读进程不读走管道缓冲区中的数据,那么写操作将会一直阻塞。
有名管道文件类型 p
mkfifo open write read close
参数1 创建的fifo文件名
参数2 创建fifo文件的权限 , 例如,0666 ,创建后是0664 0666 &(~umask)
***************注意:读端不存在时管道无意义。
信号
1.信号是软件层次上对中断机制的一种模拟,在原理上,一个信号收到一个信号与处理器收到一个中断请求可以说是一样的。
信号是异步的,一个进程不必通过任何操作来等待信号的到来,进程也不知道信号到底什么时候到达。
信号可以直接进行用户空间进程和内核进程的交互,内核进程也可以利用它来通知用户空间发生了哪些系统事件。
2.信号是进程间通信机制中唯一的异步通信方式。
3.信号的产生有硬件来源(比如按下了键盘和其他硬件故障)和软件来源(一些函数kill,raise,alarm,非法运算等等)。
1 执行过程 信号的默认处理函数
int kill(pid_t pid,int sig)
pid : 要接收信号的进程的进程号
0 : 信号被发送到所有和pid进程在同一个进程组的进程
-1 : 信号发给所有的进程表中的进程(处了进程号最大的进程外)
2 kill
参数1 进程ID
参数2 信号值
raise 给自己发信号
- #include
- #include
- #include
- #include
- #include
- int main()
- {
- pid_t pid;
- int ret;
- //创建一子进程
- if ((pid = fork()) < 0)
- {
- printf("Fork error\r\n");
- exit(-1);
- }
- if (pid == 0)
- {
- //在子进程中使用raise()函数发出SIGSTOP信号,使子进程暂停
- printf("child (pid : %d) is waiting for any signal\r\n",getpid());
- raise(SIGSTOP);
- exit(0);
- }
- else
- {
- //在父进程中收集子进程的状态,并调用kill()函数发送信号
- if ((waitpid(pid,NULL,WNOHANG)) == 0)
- {
- kill(pid,SIGKILL);
- printf("parent kill child process %d\r\n",pid);
- }
- waitpid(pid,NULL,0);
- exit(0);
- }
- }
3 alarm
定时器,参数 设定多少秒后产生SIGALRM信号,此信号默认处理是结束进程,
一个进程只能有一个闹钟时间,如果在调用alarm()之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替
pause()函数是用于将调用进程挂起直至接收到信号为止。
此函数等待一个信号产生后继续执行,否则挂起进程(此时cpu利用率很低,接近于0)
- #include
- #include
- #include
- int main()
- {
- //调用alarm函数
- alarm(5);
- pause();
- printf("I have been waken up.\r\n");//此语句不会被执行
- }
4 signal
入参1 信号
入参2 信号处理函数
用于修改信号默认处理函数
5 atoi 数字字符串转成整型
共享内存
共享的是内核里的内存3-4g
1 创建 shmget
入参1 key 通过ftok获取
入参2 size 共享内存大小,字节为单位
入参3 shmflg IPC_CREAT | 0666
返回值 共享内存标识符 int类型
2 映射 shm_at
入参1 内存标识符,shmget 返回值
入参2 指定的映射地址,通常用NULL,使用shmat返回的映射地址
入参3 0 共享内存可读写
3 取消映射 shmdt
入参1 shm_at映射的地址
4 删除 shmctl
入参1 内存标识符
入参2 IPC_RMID
入参3 IPC_RMID时给NULL
msg_send.c
- #include
- #include
- #include
- #include
- #include
- #include
- #define BUF_SIZE 10
- struct MyBuf
- {
- long m_lType;
- char buf[BUF_SIZE];
- };
- int main()
- {
- key_t key = ftok(".", 15);
- if (-1 == key)
- {
- return -1;
- }
- printf("ftok ok\r\n");
- int iMsgID = msgget(key, IPC_CREAT | 0666);
- if (-1 == iMsgID)
- {
- return -1;
- }
- printf("msgget ok\r\n");
- //send msg
- struct MyBuf stBuf;
- memset(&stBuf, 0, sizeof(struct MyBuf));
- stBuf.m_lType = 6;
- strcpy(stBuf.buf, "ttt");
- int iRet = msgsnd(iMsgID, (void *)&stBuf, BUF_SIZE, 0);
- printf("%d\r\n", iRet);
- return 0;
- }
msg_recive.c
- #include
- #include
- #include
- #include
- #include
- #include
- #define BUF_SIZE 10
- struct MyBuf
- {
- long m_lType;
- char buf[BUF_SIZE];
- };
- int main()
- {
- key_t key = ftok(".", 15);
- if (-1 == key)
- {
- return -1;
- }
- printf("ftok ok\r\n");
- int iMsgID = msgget(key, IPC_CREAT | 0666);
- if (-1 == iMsgID)
- {
- return -1;
- }
- printf("msgget ok\r\n");
- //receive msg
- struct MyBuf stBuf;
- memset(&stBuf, 0, sizeof(struct MyBuf));
- int iRet = msgrcv(iMsgID, (void *)&stBuf, BUF_SIZE,6, 0);
- printf("%d %s\r\n", iRet, stBuf.buf);
- return 0;
- }
- #include
- #include
- #include
- #include
- #include
- #include
- #define BUFFER_SIZE 2048
- int main()
- {
- pid_t pid;
- int shmid;
- char *shm_addr;
- char flag[] = "WROTE";
- char buff[BUFFER_SIZE];
- //创建共享内存
- if ((shmid = shmget(IPC_PRIVATE,BUFFER_SIZE,0666)) < 0)
- {
- perror("shmget\r\n");
- exit(1);
- }
- else
- {
- printf("Create shared-memory:%d\r\n",shmid);
- }
- //显示共享内存情况
- system("ipcs -m");
- pid = fork();
- if (pid == -1)
- {
- perror("fork\r\n");
- exit(1);
- }
- else if (pid == 0)//子进程处理
- {
- //映射共享内存
- if ((shm_addr = shmat(shmid,0,0)) == (void *)-1)
- {
- perror("Child:shmat\r\n");
- exit(1);
- }
- else
- {
- printf("Child : Attach shared-memory: %p\r\n",shm_addr);
- }
- system("ipcs -m");
- //通过检查在共享内存的头部是否标志字符串WROTE来确认父进程已经向共享内存写入有效数据
- while (strncmp(shm_addr,flag,strlen(flag)))
- {
- printf("Child:wait for enable data...\r\n");
- sleep(5);
- }
- strcpy(buff,shm_addr+strlen(flag));
- printf("Child : shared-memory : %s\r\n",buff);
- //解除共享内存映射
- if ((shmdt(shm_addr)) < 0)
- {
- perror("shmdt");
- exit(1);
- }
- else
- {
- printf("Child : Deattach shared-memory\r\n");
- }
- system("ipcs -m");
- //删除共享内存
- if (shmctl(shmid,IPC_RMID,NULL) == -1)
- {
- perror("Child: shmctl(IPC_RMID)\r\n");
- exit(1);
- }
- else
- {
- printf("Delete shared-memory\r\n");
- }
- system("ipcs -m");
- }
- else //父进程处理
- {
- //映射共享内存
- if ((shm_addr = shmat(shmid,0,0)) == (void *)-1)
- {
- perror("Parent : shmat");
- exit(1);
- }
- else
- {
- printf("Parent: Attach shared-memory : %p\r\n",shm_addr);
- }
- sleep(1);
- printf("\nInput some string:\r\n");
- fgets(buff,BUFFER_SIZE,stdin);
- strncpy(shm_addr + strlen(flag),buff,strlen(buff));
- strncpy(shm_addr,flag,strlen(flag));
- //解除共享内存映射
- if ((shmdt(shm_addr)) < 0)
- {
- perror("Parent: shmdt");
- exit(1);
- }
- else
- {
- printf("Parent: Deattach shared-memory\r\n");
- }
- system("ipcs -m");
- waitpid(pid,NULL,0);
- printf("Finished\r\n");
- }
- exit(0);
- }
查看共享内存命令 ipcs
- #include
- #include
- #include
- #include
- #include
- #include
- #define MAX_DATA_LEN 256
- int main()
- {
- pid_t pid;
- int pipe_fd[2];
- char buf[MAX_DATA_LEN];
- const char data[] = "Pipe test program";
- int real_read, real_write;
- memset(buf,0,sizeof(buf));
- if (pipe(pipe_fd) < 0)
- {
- perror("fail to pipe");
- exit(-1);
- }
- if ((pid = fork()) == 0)//创建子进程
- {
- close(pipe_fd[1]);//子进程关闭写描述符,并通过使子进程暂停1s等待父进程关闭相应的读描述符
- sleep(1);
- if ((real_read = read(pipe_fd[0],buf,MAX_DATA_LEN)) > 0)//子进程读取管道内容
- {
- printf("%d byte read from the pipe is '%s'\r\n",real_read,buf);
- }
- close(pipe_fd[0]);//关闭子进程读描述符
- exit(0);
- }
- else if (pid > 0)
- {
- close(pipe_fd[0]);//父进程关闭读描述符
- if ((real_write = write(pipe_fd[1],data,strlen(data))) != -1)
- {
- printf("parent wrote %d bytes:'%s'\r\n",real_write,data);
- }
- close(pipe_fd[1]);//关闭父进程写描述符
- waitpid(pid,NULL,0);//收集子进程退出信息
- exit(0);
- }
信号
信号是在软件层次上对中断机制的一种模拟。
int kill(pid_t pid, int sig);
pid>0时发送信号给进程号为pid的进程
pid = 0:信号被发送到所有和当前进程在同一个进程组的进程
pid = -1 : 信号发给所有进程表中的进程
pid < -1 : 信号发给进程组号为-pid的每一个进程
- #include
- #include
- #include
- #include
- #include
- int main()
- {
- pid_t pid;
- int ret;
- //创建子进程
- if ((pid = fork()) < 0)
- {
- printf("Fork error\n");
- exit(1);
- }
- if (pid == 0)
- {
- //在子进程中使用raise()函数发出SIGSTOP信号,使子进程暂停
- printf("child(pid : %d) is waiting for any signal\n",getpid());
- raise(SIGSTOP);
- exit(0);
- }
- else
- {
- //在父进程中收集子进程的状态,并调用kill()函数发送信号
- if((waitpid(pid, NULL, WNOHANG)) == 0)
- {
- kill(pid, SIGKILL);
- printf("parent kill child process %d\n",pid);
- }
- waitpid(pid, NULL, 0);
- exit(0);
- }
- }
消息队列
1 创建
msgget
入参1 key,用ftok创建
入参2 权限, IPC_CREAT | 0666
返回值 成功返回 消息队列ID,失败返回-1, 可用ipcs命令查看
2 发消息
msgsnd
入参1 消息队列ID
入参2 消息结构msgbuf如下:
struct msgbuf
{
long mtype; //消息类型,必须大于0
char mtext[N]}; //消息正文
}
入参3 ***消息正文大小,需要注意这里
参数3 通常使用0,消息发送完成后返回
返回值, 0 成功, -1 失败
3 收消息
msgrcv
参数1 消息队列id
参数2 接受消息的结构体指针,同自定义的struct msgbuf
参数3 ***消息正文大小
参数4 消息类型
0:接收消息队列中第一个消息。
大于0:接收消息队列中第一个类型为msgtyp的消息.
小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
参数5
通常使用0, 若无消息函数会一直阻塞
4 msgctl
删除消息队列 msgctl(消息队列ID, IPC_RMID, NULL)
信号量
1 创建
semget
入参1 key 通过ftok产生
入参2 信号灯集中信号灯的个数
入参3 权限 IPC_CREAT | 0666
返回值 成功:信号灯集ID,失败 -1
2 初始化
semctl 通过此函数可以设置信号灯集中某个信号的初始值
参数1 信号灯集ID
参数2 修改信号灯集中的信号灯编号
参数3 命令
cmd: GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
IPC_RMID:从系统中删除信号灯集合
设置信号灯初值时用到第四个参数
union semun arg,此结构体在中有定义,中无定义
参照man semctl函数说明中关于union semun共同体说明
3 semop PV操作
参数1 信号灯集ID
参数2 struct sembuf
struct sembuf {
short sem_num; // 要操作的信号灯的编号,索引从0开始
short sem_op; // 0 : 等待,直到信号灯的值变成0
// 1 : 释放资源,V操作
// -1 : 分配资源,P操作
short sem_flg; // 0, IPC_NOWAIT, SEM_UNDO
};
参数3 操作的信号灯个数
4 删除
semctl(信号灯集ID, 0, IPC_RMID)
有名信号灯
sem_open
参数1 信号灯名
参数2 O_CREAT 如果不存在则创建,如指定此参数,当有名信号存在情况下忽略参数3和参数4
参数3 0666 权限
参数4 0 / 1 初始值
sem_close
关闭一个有名信号灯
编译 需要加 -lpthread
code
msgrecv.c
- #include
- #include
- #include
- #include
- #include
- #include
- #define BUF_SIZE 10
- struct MyBuf
- {
- long m_lType;
- char buf[BUF_SIZE];
- };
- int main()
- {
- key_t key = ftok(".", 15);
- if (-1 == key)
- {
- return -1;
- }
- printf("ftok ok\r\n");
- int iMsgID = msgget(key, IPC_CREAT | 0666);
- if (-1 == iMsgID)
- {
- return -1;
- }
- printf("msgget ok\r\n");
- //receive msg
- struct MyBuf stBuf;
- memset(&stBuf, 0, sizeof(struct MyBuf));
- int iRet = msgrcv(iMsgID, (void *)&stBuf, BUF_SIZE,6, 0);
- printf("%d %s\r\n", iRet, stBuf.buf);
- return 0;
- }
- msg_send
- #include
- #include
- #include
- #include
- #include
- #include
- #define BUF_SIZE 10
- struct MyBuf
- {
- long m_lType;
- char buf[BUF_SIZE];
- };
- int main()
- {
- key_t key = ftok(".", 15);
- if (-1 == key)
- {
- return -1;
- }
- printf("ftok ok\r\n");
- int iMsgID = msgget(key, IPC_CREAT | 0666);
- if (-1 == iMsgID)
- {
- return -1;
- }
- printf("msgget ok\r\n");
- //send msg
- struct MyBuf stBuf;
- memset(&stBuf, 0, sizeof(struct MyBuf));
- stBuf.m_lType = 6;
- strcpy(stBuf.buf, "ttt");
- int iRet = msgsnd(iMsgID, (void *)&stBuf, BUF_SIZE, 0);
- printf("%d\r\n", iRet);
- return 0;
- }
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #define BUF_SIZE 10
- int main()
- {
- key_t key = ftok(".", 16);
- if (-1 == key)
- {
- return -1;
- }
- printf("ftok ok\r\n");
- //create sem
- int iSemID = semget(key, 1, IPC_CREAT | 0666);
- if (-1 == iSemID)
- {
- return -1;
- }
- printf("semget ok\r\n");
- //set value
- union semun stSemInit;
- stSemInit.val = 1;
- int iRet = semctl(iSemID, 0, SETVAL, stSemInit);
- if (-1 == iRet)
- {
- semctl(iSemID, 0, IPC_RMID);
- return -1;
- }
- printf("set val ok\r\n");
- // p v
- pid_t pid = fork();
- if (pid < 0)
- {
- semctl(iSemID, 0, IPC_RMID);
- return -1;
- }
- struct sembuf stBuf;
- if (0 == pid)
- {
- stBuf.sem_num = 0;
- stBuf.sem_op = -1;
- stBuf.sem_flg = SEM_UNDO;
- semop(iSemID, &stBuf, 1);
- printf("hello\r\n");
- sleep(5);
- stBuf.sem_num = 0;
- stBuf.sem_op = 1;
- stBuf.sem_flg = SEM_UNDO;
- semop(iSemID, &stBuf, 1);
- exit(0);
- }
- else
- {
- sleep(1);
- stBuf.sem_num = 0;
- stBuf.sem_op = -1;
- stBuf.sem_flg = SEM_UNDO;
- semop(iSemID, &stBuf, 1);
- printf("world\r\n");
- stBuf.sem_num = 0;
- stBuf.sem_op = 1;
- stBuf.sem_flg = SEM_UNDO;
- semop(iSemID, &stBuf, 1);
- wait(NULL);
- semctl(iSemID, 0, IPC_RMID);
- }
- return 0;
- }
sem_a.c
- #include
- #include /* For O_* constants */
- #include /* For mode constants */
- #include
- int main()
- {
- sem_t *pSem1 = sem_open("aaa", O_CREAT, 0666, 1);
- if (SEM_FAILED == pSem1)
- {
- return -1;
- }
- sem_t *pSem2 = sem_open("bbb", O_CREAT, 0666, 0);
- if (SEM_FAILED == pSem2)
- {
- sem_close(pSem1);
- return -1;
- }
- while(1)
- {
- sem_wait(pSem1);
- printf("hello\r\n");
- sleep(1);
- sem_post(pSem2);
- }
- }
sem_b.c
- #include
- #include /* For O_* constants */
- #include /* For mode constants */
- #include
- int main()
- {
- sem_t *pSem1 = sem_open("aaa", O_CREAT, 0666, 1);
- if (SEM_FAILED == pSem1)
- {
- return -1;
- }
- sem_t *pSem2 = sem_open("bbb", O_CREAT, 0666, 0);
- if (SEM_FAILED == pSem2)
- {
- sem_close(pSem1);
- return -1;
- }
- while(1)
- {
- sem_wait(pSem2);
- printf("world\r\n");
- sleep(1);
- sem_post(pSem1);
- }
- }
共享内存函数(shmget、shmat、shmdt、shmctl
共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成。下面的表格列出了这四个函数的函数原型及其具体说明。
1. shmget函数原型
shmget(得到一个共享内存标识符或创建一个共享内存对象) |
所需头文件 |
#include #include #include |
函数说明 |
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符 |
函数原型 |
int shmget(key_t key, size_t size, int shmflg) |
函数传入值 |
key |
0(IPC_PRIVATE):会建立新共享内存对象 |
大于0的32位整数:视参数shmflg来确定操作。通常要求此值来源于ftok返回的IPC键值 |
size |
大于0的整数:新建的共享内存大小,以字节为单位 |
0:只获取共享内存时指定为0 |
shmflg |
0:取共享内存标识符,若不存在则函数会报错 |
IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符 |
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错 |
函数返回值 |
成功:返回共享内存的标识符 |
出错:-1,错误原因存于error中 |
附加说明 |
上述shmflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限 |
错误代码 |
EINVAL:参数size小于SHMMIN或大于SHMMAX EEXIST:预建立key所指的共享内存,但已经存在 EIDRM:参数key所指的共享内存已经删除 ENOSPC:超过了系统允许建立的共享内存的最大值(SHMALL) ENOENT:参数key所指的共享内存不存在,而参数shmflg未设IPC_CREAT位 EACCES:没有权限 ENOMEM:核心内存不足 |
在Linux环境中,对开始申请的共享内存空间进行了初始化,初始值为0x00。
如果用shmget创建了一个新的消息队列对象时,则shmid_ds结构成员变量的值设置如下:
Ÿ shm_lpid
、shm_nattach
、shm_atime、shm_dtime
设置为0。
Ÿ msg_ctime设置为当前时间。
Ÿ shm_segsz
设成创建共享内存的大小。
Ÿ shmflg的读写权限放在shm_perm.mode中。
Ÿ shm_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。
2. shmat函数原型
shmat(把共享内存区对象映射到调用进程的地址空间) |
所需头文件 |
#include #include #include |
函数说明 |
连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问 |
函数原型 |
void *shmat(int shmid, const void *shmaddr, int shmflg) |
函数传入值 |
shmid |
共享内存标识符 |
shmaddr |
指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置 |
shmflg |
SHM_RDONLY:为只读模式,其他为读写模式 |
函数返回值 |
成功:附加好的共享内存地址 |
出错:-1,错误原因存于error中 |
附加说明 |
fork后子进程继承已连接的共享内存地址。exec后该子进程与已连接的共享内存地址自动脱离(detach)。进程结束后,已连接的共享内存地址会自动脱离(detach) |
错误代码 |
EACCES:无权限以指定方式连接共享内存 EINVAL:无效的参数shmid或shmaddr ENOMEM:核心内存不足 |
3. shmdt函数原型
shmat(断开共享内存连接) |
所需头文件 |
#include #include #includ |
函数说明 |
与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存 |
函数原型 |
int shmdt(const void *shmaddr) |
函数传入值 |
shmaddr:连接的共享内存的起始地址 |
函数返回值 |
成功:0 |
出错:-1,错误原因存于error中 |
附加说明 |
本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程 |
错误代码 |
EINVAL:无效的参数shmaddr |
4. shmctl函数原型
shmctl(共享内存管理) |
所需头文件 |
#include #include #include |
函数说明 |
完成对共享内存的控制 |
函数原型 |
int shmctl(int shmid, int cmd, struct shmid_ds *buf) |
函数传入值 |
shmid |
共享内存标识符 |
cmd |
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中 |
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内 |
IPC_RMID:删除这片共享内存 |
buf |
共享内存管理结构体。具体说明参见共享内存内核结构定义部分 |
函数返回值 |
成功:0 |
出错:-1,错误原因存于error中 |
错误代码 |
EACCESS:参数cmd为IPC_STAT,确无权限读取该共享内存 EFAULT:参数buf指向无效的内存地址 EIDRM:标识符为msqid的共享内存已被删除 EINVAL:无效的参数cmd或shmid EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行 |
共享内存应用范例
5. 父子进程通信范例
父子进程通信范例,shm.c源代码如下:
- #include
- #include
- #include
- #include
- #include
- #include
- #define SIZE 1024
- int main()
- {
- int shmid ;
- char *shmaddr ;
- struct shmid_ds buf ;
- int flag = 0 ;
- int pid ;
-
- shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT|0600 ) ;
- if ( shmid < 0 )
- {
- perror("get shm ipc_id error") ;
- return -1 ;
- }
- pid = fork() ;
- if ( pid == 0 )
- {
- shmaddr = (char *)shmat( shmid, NULL, 0 ) ;
- if ( (int)shmaddr == -1 )
- {
- perror("shmat addr error") ;
- return -1 ;
-
- }
- strcpy( shmaddr, "Hi, I am child process!\n") ;
- shmdt( shmaddr ) ;
- return 0;
- } else if ( pid > 0) {
- sleep(3 ) ;
- flag = shmctl( shmid, IPC_STAT, &buf) ;
- if ( flag == -1 )
- {
- perror("shmctl shm error") ;
- return -1 ;
- }
-
- printf("shm_segsz =%d bytes\n", buf.shm_segsz ) ;
- printf("parent pid=%d, shm_cpid = %d \n", getpid(), buf.shm_cpid ) ;
- printf("chlid pid=%d, shm_lpid = %d \n",pid , buf.shm_lpid ) ;
- shmaddr = (char *) shmat(shmid, NULL, 0 ) ;
- if ( (int)shmaddr == -1 )
- {
- perror("shmat addr error") ;
- return -1 ;
-
- }
- printf("%s", shmaddr) ;
- shmdt( shmaddr ) ;
- shmctl(shmid, IPC_RMID, NULL) ;
- }else{
- perror("fork error") ;
- shmctl(shmid, IPC_RMID, NULL) ;
- }
-
- return 0 ;
- }
编译 gcc shm.c –o shm。
执行 ./shm,执行结果如下:
shm_segsz =1024 bytes
shm_cpid = 9503
shm_lpid = 9504
Hi, I am child process!
6. 多进程读写范例
多进程读写即一个进程写共享内存,一个或多个进程读共享内存。下面的例子实现的是一个进程写共享内存,一个进程读共享内存。
(1)下面程序实现了创建共享内存,并写入消息。
shmwrite.c源代码如下:
- #include
- #include
- #include
- #include
- #include
- #include
- typedef struct{
- char name[8];
- int age;
- } people;
- int main(int argc, char** argv)
- {
- int shm_id,i;
- key_t key;
- char temp[8];
- people *p_map;
- char pathname[30] ;
-
- strcpy(pathname,"/tmp") ;
- key = ftok(pathname,0x03);
- if(key==-1)
- {
- perror("ftok error");
- return -1;
- }
- printf("key=%d\n",key) ;
- shm_id=shmget(key,4096,IPC_CREAT|IPC_EXCL|0600);
- if(shm_id==-1)
- {
- perror("shmget error");
- return -1;
- }
- printf("shm_id=%d\n", shm_id) ;
- p_map=(people*)shmat(shm_id,NULL,0);
- memset(temp, 0x00, sizeof(temp)) ;
- strcpy(temp,"test") ;
- temp[4]='0';
- for(i = 0;i<3;i++)
- {
- temp[4]+=1;
- strncpy((p_map+i)->name,temp,5);
- (p_map+i)->age=0+i;
- }
- shmdt(p_map) ;
- return 0 ;
- }
(2)下面程序实现从共享内存读消息。
shmread.c源代码如下:
- #include
- #include
- #include
- #include
- #include
- #include
- typedef struct{
- char name[8];
- int age;
- } people;
- int main(int argc, char** argv)
- {
- int shm_id,i;
- key_t key;
- people *p_map;
- char pathname[30] ;
-
- strcpy(pathname,"/tmp") ;
- key = ftok(pathname,0x03);
- if(key == -1)
- {
- perror("ftok error");
- return -1;
- }
- printf("key=%d\n", key) ;
- shm_id = shmget(key,0, 0);
- if(shm_id == -1)
- {
- perror("shmget error");
- return -1;
- }
- printf("shm_id=%d\n", shm_id) ;
- p_map = (people*)shmat(shm_id,NULL,0);
- for(i = 0;i<3;i++)
- {
- printf( "name:%s\n",(*(p_map+i)).name );
- printf( "age %d\n",(*(p_map+i)).age );
- }
- if(shmdt(p_map) == -1)
- {
- perror("detach error");
- return -1;
- }
- return 0 ;
- }
(3)编译与执行
① 编译gcc shmwrite.c -o shmwrite。
② 执行./shmwrite,执行结果如下:
key=50453281
shm_id=688137
③ 编译gcc shmread.c -o shmread。
④ 执行./shmread,执行结果如下:
key=50453281
shm_id=688137
name:test1
age 0
name:test2
age 1
name:test3
age 2
⑤ 再执行./shmwrite,执行结果如下:
key=50453281
shmget error: File exists
⑥ 使用ipcrm -m 688137删除此共享内存。