Linux环境编程学习

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2017/04/30/Linux环境编程/#more

记录Linux环境下的编程,0基础开始,不断更新。

最近在学习Linux环境编程,仿佛打开了新世界的大门。初学,认知有局限,不断修正。

文件相关

1.fopen

{% codeblock lang:c [1-fopen.c] https://github.com/hceng/learn/blob/master/io/day1/1-fopen.c %}
#include <stdio.h>

int main(int argc, const char *argv[])
{
FILE * fp;
unsigned int i = 0;

fp = fopen(argv[1], "w");
if(fp == NULL)
{
	printf("Unable to fopen \n");
	return -1;
}

/* printf(“argc = %d\n”,argc);
for(i; i<argc; i++)
{
printf(“argv[%d]=%s\n”,i,argv[i]);
}
*/ printf(“read success \n”);

fclose(fp);
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  fopen的第一个参数:argv[1],是运行fopen时传入的参数 hceng.txt.fopen对其写操作,但没有这个文件,就自动创建了该文件。

2.fopen_max

{% codeblock lang:c [2-fopen_max.c] https://github.com/hceng/learn/blob/master/io/day1/2-fopen_max.c%}
#include <stdio.h>

int main(int argc, const char *argv[])
{
FILE * fp;
int count = 0;
fclose(stdin);
while((fp = fopen(argv[1], “r”)) != NULL)
count++;

printf("count = %d \n", count);

return 0;

}
{% endcodeblock %}
测试结果:

分析:
  意味着一个文件最多被打开1022次。

3. fgetc

{% codeblock lang:c [3-fgetc.c] https://github.com/hceng/learn/blob/master/io/day1/3-fgetc.c%}
#include <stdio.h>

int main(int argc, const char *argv[])
{
FILE * fp;

fp = fopen(argv[1], "r");
if(fp == NULL)
{
	printf("Unable to fopen\n");
	return -1;
}

char ch = fgetc(fp);
 ch = fgetc(fp);
 ch = fgetc(fp);

//printf("ch = %c \n", ch);

fputc(ch, stdout);
putchar(10);
fclose(fp);
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  fgetc每次得到一个字符(且自动跳到下一个字符),fputc这里指定ch写到标准输出流。putchar是输出一个字符的意思,这里参数10换成ascii码是换行。

4. cat

{% codeblock lang:c [4-cat.c] https://github.com/hceng/learn/blob/master/io/day1/4-cat.c%}
#include <stdio.h>

int main(int argc, const char *argv[])
{
FILE *fp_r;
char ch;

fp_r = fopen(argv[1], "r");
if(fp_r == NULL)
{
	//	printf("Unable to fopen \n");
	fprintf(stdout, "Unable to fopen \n");
	return -1;
}

while((ch = fgetc(fp_r)) != EOF)
{
	//printf("%c",ch);
	fputc(ch, stdout);
}
fclose(fp_r);
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  实现类似cat的命令,fgetc一直获取字符到结尾,然后fputc依次打印出来。

5. cp

{% codeblock lang:c [5-cp.c] https://github.com/hceng/learn/blob/master/io/day1/5-cp.c%}
#include <stdio.h>

int main(int argc, const char *argv[])
{
FILE *fp_r, *fp_w;
char ch;

fp_r = fopen(argv[1], "r");
if(fp_r == NULL)
{
	fprintf(stdout,"Unable to fopen fp_r\n");
	return -1;
}

fp_w = fopen(argv[2], "w");
if(fp_w == NULL)
{
	fprintf(stdout, "Unable to fopen fp_w \n");
}

while((ch = fgetc(fp_r)) != EOF)
{
	fputc(ch, fp_w);
}


fclose(fp_r);
fclose(fp_w);
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  实现类似cp的命令,fgetc一直获取argv[1]字符到结尾,然后fputc写到argv[2]。

6. line

{% codeblock lang:c [6-line.c] https://github.com/hceng/learn/blob/master/io/day1/6-line.c %}
#include <stdio.h>

int main(int argc, const char *argv[])
{
FILE *fp_r;
char ch;
int line = 0;

fp_r = fopen(argv[1], "r");
if(fp_r == NULL)
{
	fprintf(stdout, "Unable to fopen \n");
	return -1;
}

while((ch = fgetc(fp_r)) != EOF)
{
	if(ch == '\n')
		line++;
}

printf("line = %d \n",line);
fclose(fp_r);

return 0;

}
{% endcodeblock %}
测试结果:

分析:
  获取文件行数,fgetc一直读到结尾,遇到\n则表示换行,即行数+1。

7. fgets

{% codeblock lang:c [7-fgets.c] https://github.com/hceng/learn/blob/master/io/day1/7-fgets.c %}
#include <stdio.h>

#define N 32

int main(int argc, const char *argv[])
{
FILE *fp_r;
char buf[N] = {0};

fp_r = fopen(argv[1], "r");
if(fp_r == NULL)
{
	fprintf(stdout, "Unable to fopen fp_r \n");
	return -1;
}

fgets(buf, 32, fp_r);

fputs(buf, stdout);

fclose(fp_r);
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  获取文件字符串,这里要设置读取的字符串的长度,感觉有点局限。应该是识别以\n结尾,这里一次只读取了一行。

8. fgets_cp

{% codeblock lang:c [8-fgets_cp.c] https://github.com/hceng/learn/blob/master/io/day1/8-fgets_cp.c%}
#include <stdio.h>

#define N 32

int main(int argc, const char *argv[])
{
FILE *fp_r, *fp_w;
char buf[N] = {0};

fp_r = fopen(argv[1], "r");
if(fp_r == NULL)
{
	fprintf(stdout, "Unable to fopen fp_r \n");
	return -1;

}
fp_w = fopen(argv[2], "w");
if(fp_w == NULL)
{
	fprintf(stdout, "Unable to fopen fp_w \n");
	return -1;
}

while(fgets(buf, 16, fp_r) != NULL)
	fputs(buf, fp_w);
fclose(fp_r);
fclose(fp_w);

return 0;

}
{% endcodeblock %}
测试结果:

分析:
  以fgets的方式获取一行,写入另一个文件,以实现cp的功能。判断的依据是fgets获取的是非空

9. fread_fwrite

{% codeblock lang:c [9-fread_fwrite.c] https://github.com/hceng/learn/blob/master/io/day1/9-fread_fwrite.c %}
#include <stdio.h>

#define N 32

int main(int argc, const char *argv[])
{
FILE *fp_r, *fp_w;
char buf[N] = {0};

fp_r = fopen(argv[1], "r");
if(fp_r == NULL)
{
	fprintf(stdout, "Unable to fopen fp_r\n");
	return -1;
}

fp_w = fopen(argv[2], "w");
if(fp_w == NULL)
{
	fprintf(stdout, "Unable to fopen fp_w\n");
	return -1;
}
size_t bytes = fread(buf, 5, 3, fp_r);
fwrite(buf, 5, 2, fp_w);

//printf("bytes = %d \n",bytes);
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  fread/ fwrite第一个参数是指向读取/写入的缓冲数据,第二个参数是定义每个数据类型的大小,第三个是定义的数据类型个数,最后是指向操作的文件指针。

10.实例

时间编程题目要求:编程读写一个文件test.txt,每隔1秒向文件中写入一行数据,类似这样:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
该程序应该无限循环,直到按Ctrl-C中断程序。
再次启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
3, 2007-7-30 15:19:02
4, 2007-7-30 15:19:03
5, 2007-7-30 15:19:04

提示:
要追加写入文件,同时要读取该文件的内容以决定下一个序号是几,应该用什么模式打开文件?
首先判断一下打开的文件是否为新文件,如果是新文件,就从序号1开始写入;如果不是新文件,则统计原来有多少行,比如有n行,然后从序号n+1开始写入。以后每写一行就把行号加1。
获取当前的系统时间需要调用函数time(),得到的结果是一个time_t类型,其实就是一个大整数,其值表示从UTC时间1970年1月1日00:00:00(称为UNIX的Epoch时间)到当前时刻的秒钟数。然后调用localtime()将time_t所表示的UTC时间转换为本地时间(我们是+8区,比UTC多8个小时)并转成struct tm类型,该类型的各数据成员分别表示年月日时分秒,请自己写出转换格式的代码,不要使用ctime()或asctime()函数。
具体用法请查阅man page。time和localtime函数需要头文件time.h。
调用sleep(n)可使程序睡眠n秒,该函数需要头文件unistd.h

{% codeblock lang:c [hceng_time.c] https://github.com/hceng/learn/blob/master/io/day2/hceng_time.c%}
/*************************************************************************
> File Name: hceng_time.c
> Author:hceng
> Mail: huangcheng.job@foxmail.com
> Created Time: Wed 26 Apr 2017 06:48:22 AM UTC
************************************************************************/

#include <stdio.h>
#include <time.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
FILE *fd;
char ch;
unsigned int line = 0;
time_t *get_time;
struct tm *local_time;

//add data to file
fd = fopen(argv[1], "a+");
if(fd == NULL)
{
	fprintf(stderr,"can't open file!");
	return -1;
}

//get line
while((ch = fgetc(fd)) != EOF)
{
	if(ch == '\n')
		++line;
}
//show
while(1)
{
	time(get_time);
	local_time = localtime(get_time);
	fprintf(fd,"%d. %d-%d-%d  %d:%d:%d\n",++line,local_time->tm_year+1900,local_time->tm_mon,local_time->tm_mday,local_time->tm_hour,local_time->tm_min,local_time->tm_sec);
	printf("%d. %d-%d-%d  %d:%d:%d\n",line,local_time->tm_year+1900,local_time->tm_mon,local_time->tm_mday,local_time->tm_hour,local_time->tm_min,local_time->tm_sec);
	fflush(fd);
	sleep(1);
}

fclose(fd);
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  先以追加的方式打开文件(没有则创建),然后获取文件行数,再获取时间且转换成本地时间,然后按格式要求打印信息。


进程相关

1.fork1

{% codeblock lang:c [fork.c] https://github.com/hceng/learn/blob/master/io/day3/fork.c%}
#include <unistd.h>
#include <stdio.h>

int main(int argc, const char *argv[])
{

pid_t pid;

printf("hello world\n");
pid = fork();
printf("pid = %d\n",pid);

if(pid < 0)
{
	perror("Unable to fork");
	return -1;
}

if(pid == 0)
{
	//子进程
	printf("child process parent %d ,child = %d\n",getppid(), getpid());
}
else
{
	//父进程:
	printf("parent process = %d, child = %d\n", getpid(), pid);
}
while(1);
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  首先父进程fork了自己,返回了子进程的PID,并打印出来。然后继续执行,打印出自己的PID和子进程PID。子进程产生了和父进程一模一样的代码,然后从fork处开始执行,子进程fork返回的的固定为0,然后getppi打印出父进程PID,getpid打印出自己的PID。
  从PS可以看到父进程PID为:11370,子进程为:11371.
  注:一般fork后是父进程继续运行,但如果父进程时间片用完了,则子进程会先进行,所以这里的打印顺序是不确定的,如果要确定,需要同步。

2.fork2

{% codeblock lang:c [fork_1.c] https://github.com/hceng/learn/blob/master/io/day3/fork_1.c%}
#include <unistd.h>
#include <stdio.h>

int main(int argc, const char *argv[])
{

pid_t pid;
pid = fork();

if(pid < 0)
{
	perror("Unable to fork");
	return -1;
}

if(pid == 0)
{
	//子进程
	printf("child process parent %d ,child = %d\n",getppid(), getpid());
}
else
{
	//父进程:
	printf("parent process = %d, child = %d\n", getpid(), pid);
	while(1);
}
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  这里父进程有个whileh会一直执行,二子程序没有。在PS中可以看到子进程(12173)被标记为defunct(僵尸进程)。
  在Linux系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他,那么他将变成一个僵尸进程。当用ps命令观察进程的执行状态时,看到这些进程的状态栏为defunct。僵尸进程是一个早已死亡的进程,但在进程表(processs table)中仍占了一个位置(slot)。
  但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程。因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程,看看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init进程来接管他,成为他的父进程,从而保证每个进程都会有一个父进程。而Init进程会自动wait其子进程,因此被Init接管的所有进程都不会变成僵尸进程。

3.fork3

{% codeblock lang:c [fork_2.c] https://github.com/hceng/learn/blob/master/io/day3/fork_2.c%}
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <wait.h>

int main(int argc, const char *argv[])
{

pid_t pid;
pid = fork();

if(pid < 0)
{
	perror("Unable to fork");
	return -1;
}

if(pid == 0)
{
	//子进程
	printf("child process parent %d ,child = %d\n",getppid(), getpid());
	exit(0);
}
else
{
	//父进程:
	int status;
	sleep(4);
	wait(&status);
	printf("child exit\n");
	sleep(4);
	printf("parent status = %d\n", status);
}
return 0;

}

{% endcodeblock %}
测试结果:

分析:
  这里多了个wait来监控子进程。当父进程执行wait时,立即被阻塞,直到子进程退出,才继续执行后续的。第一个sleep(4)时,PS看到父进程为15167,子进程15168为僵尸状态,执行wait时,自动回收僵尸进程。等回收完了,父进程结束阻塞,继续执行到结束。

4.exit和_exit

{% codeblock lang:c %}
#include <stdio.h>
#include <unistd.h>
//#include <stdlib.h>

int main(int argc, const char *argv[])
{
printf(“hello world”);
_exit(5);
//exit(5);
printf(“hello Shenzhen\n”);
return 0;
}

{% endcodeblock %}

{% codeblock lang:c %}
#include <stdio.h>
//#include <unistd.h>
#include <stdlib.h>

int main(int argc, const char *argv[])
{
printf(“hello world”);
//_exit(5);
exit(5);
printf(“hello Shenzhen\n”);
return 0;
}
{% endcodeblock %}

{% codeblock lang:c %}
#include <stdio.h>
#include <unistd.h>
//#include <stdlib.h>

int main(int argc, const char *argv[])
{
printf(“hello world\n”);
_exit(5);
//exit(5);
printf(“hello Shenzhen\n”);
return 0;
}
{% endcodeblock %}

分析:
  _exit()执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin, stdout, stderr …)。exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新流。因此才会看到遇到_exit并不会打印,因为数据在“IO缓存”,而exit会刷新流,导致打印输出。
  带三个例子加个\n,使用_exit也打印出来了, printf函数就是使用缓冲I/O的方式,该函数在遇到“\n”换行符时自动的从缓冲区中将记录读出。
  参考文章

5.daemon

1、何为守护进程?
(1)daemon:守护进程、 后台程序简称是d,(一般带有d的都是守护进程)
(2)长期运行: 从系统启动时开始运行,到系统关闭时结束(运行周期)
(3)与控制台脱离(终端):守护进程启动后,脱离控制台,不受终端控制,背后的问题是会话
(4)服务器:是一个应用程序,使用时直接调用
定义: 是一个在后台运行,并且不受终端控制的服务程序!
2、编写守护进程
(1)创建父子进程,父进程退出 (孤儿进程)
(2)在子进程中设置新的会话 (setsid)
(3)改变当前目录为根目录
(4)重设文件权限掩码 (增加对文件操作灵活性)
(5)关闭所有文件描述符
创建完守护进程之后,要加入服务程序,并且要在while循环之中实现。
参考文章1参考文章2

{% codeblock lang:c [dameon.c] https://github.com/hceng/learn/blob/master/io/day4/dameon.c%}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
pid_t pid;

pid = fork();

if(pid < 0)
{
	perror("Unable to fork");
	exit(1);
}

if(pid == 0)
{
	//创建新的会话,让子进程成为会话组的领导者,不受终端控制
	setsid();
	//改变当前目录为根目录
	chdir("/");
	//重设文件权限掩码
	umask(0);
	//关闭打开的文件描述符

	int i, fd_w;
	char buf[] = {"hello world\n"};

	//getdtablesize()返回这个进程的文件描述表的项数
	for(i = 0; i < getdtablesize(); i++)
		close(i);
	fd_w = open("/test123.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
	if(fd_w < 0)
	{
		perror("Unalbe to open");
		exit(1);
	}

	while(1)
	{
		write(fd_w, buf, 20);
		sleep(1);
	}

	close(fd_w);
}
else
{
	exit(0);
}
return 0;

}
{% endcodeblock %}
测试结果:

ps -ajx

分析:
  守护进程用于一直后台运行,且不受终端控制。编写守护程序有5步,参考前面的介绍。

6.exec

{% codeblock lang:c [exec.c] https://github.com/hceng/learn/blob/master/io/day4/exec.c %}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, const char *argv[])
{
printf(“hello start \n”);
#if 1
if(execl("/bin/ls", “ls”, “-l”, “-a”, NULL) < 0)
{
perror(“Unable to execl”);
exit(1);
}
#endif

#if 0
if(execlp(“ls”, “ls”, “-l”, “-a”, NULL) < 0)
{
perror(“Unable to execl”);
exit(1);
}
#endif

#if 0
char *arg[] = {“ls”, “-la”, NULL};
if(execv("/bin/ls", arg) < 0)
{
perror(“Unable to execl”);
exit(1);
}
#endif
printf(“hello end \n”);
return 0;
}
{% endcodeblock %}
测试结果:

分析:
  这里的三个exec系列函数的效果都是一样的。调用某个新程序后,就把自己覆盖了,所以最后的hello end才没打印。
  区别如下:

  带“p”的表示PATH有关;
  带“l”的表示list,与数组有关;
  带“v”的表示vector;
  带“e”的表示environment,将使用调用者的environ;

线程相关

  • 线程是什么?
    线程是一个轻量级的进程,内核也会调度线程,线程在进程当中创建。
  • 进程和线程的区别?
    线程没有自己独享的资源,因此没有自己的地址空间,他要依附在进程的地址空间中借助进程的资源运行。
    线程优点:数据共享很简单,创建线程的要快于创建进程;
    线程缺点:安全性、相互之间容易影响、有限的虚拟地址空间;
  • **线程间如何实现通信? **
    全局变量、数组
  • 线程标识符:用来唯一的表示一个线程,通过调用线程库来实现对线程的访问操作。
  • 线程相关函数:
    1、创建 --》 pthread_create
    2、回收线程 --》 pthread_join
    3、结束线程 --》 pthread_exit
    4、线程销毁 --》 pthread_destroy

1.pthread

{% codeblock lang:c %}
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:
thread 线程对象
attr 线程属性默认为NULL,栈的空间大小是8M
start_routine 函数指针,用来指向函数名
arg 参数,作为函数指针的参数
返回值:
成功: 0
失败: 错误码
{% endcodeblock %}

{% codeblock lang:c %}
void pthread_exit(void *arg)
功能: 线程结束函数
参数: 传递线程退出时的状态值
{% endcodeblock %}

{% codeblock lang:c %}
pthread_join(pthread_t pthread, void**retval)
功能:线程回收函数(阻塞等待,直到等到线程退出了,会立即释放)
参数:
pthread 线程对象
retval 线程退出时的状态值
{% endcodeblock %}

{% codeblock lang:c [pthread.c] https://github.com/hceng/learn/blob/master/io/day4/pthread.c%}
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

void *pthread_fun(void * arg)
{
int i = 20;
printf("*arg = %d \n", *(int *)arg);
while(i–)
{
printf(“pthread i = %d \n”, i);
sleep(1);
if(i == 10)
pthread_exit(“pthread exit”);
}
}

int main(int argc, const char *argv[])
{
pthread_t tid;
int num = 18;

if(pthread_create(&tid, NULL, pthread_fun, &num) != 0)
{
	perror("Unable to pthread_create");
	exit(1);
}
printf("pthread start \n");
void * ret;
pthread_join(tid, &ret);
printf("pthread end  %s \n", (char *)ret);
return 0;

}
{% endcodeblock %}

测试结果:

分析:
   pthread_create创建线程pthread_fun,并传入参数&num,然后主进程调用pthread_join等待线程。线程每隔1s打印输出,直到i = 10时,调用pthread_exit结束线程,同时传递状态值pthread exit到pthread_join。主进程再打印出状态值后退出。
   注意:编译的时候加上-pthread

2.mutex

不加互斥锁:
{% codeblock lang:c %}
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>

pthread_mutex_t lock;
int value_1, value_2;

void *pthread_fun(void * arg)
{
while(1)
{
//pthread_mutex_lock(&lock);
if(value_1 == value_2)
{
printf(“value_1 = %d, value_2 = %d\n”, value_1, value_2);
}
//pthread_mutex_unlock(&lock);
}
}

int main(int argc, const char *argv[])
{
int count = 0;
pthread_t pthread;

if(pthread_mutex_init(&lock, NULL) < 0)
{
	perror("fail to pthread_mutex_init ");
	exit(1);
}
if(pthread_create(&pthread, NULL, pthread_fun, NULL) < 0)
{
	perror("fail to pthread_create ");
	exit(1);
}
while(1)
{
	count++;
	//pthread_mutex_lock(&lock);
	value_1 = count;
	value_2 = count;
	//pthread_mutex_unlock(&lock);
}
pthread_join(pthread, NULL);

return 0;

}
{% endcodeblock %}
测试结果:

加上互斥锁:
{% codeblock lang:c [pthread_mutex.c] https://github.com/hceng/learn/blob/master/io/day4/pthread_mutex.c%}
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>

pthread_mutex_t lock;
int value_1, value_2;

void *pthread_fun(void * arg)
{
while(1)
{
pthread_mutex_lock(&lock);
if(value_1 == value_2)
{
printf(“value_1 = %d, value_2 = %d\n”, value_1, value_2);
}
pthread_mutex_unlock(&lock);
}
}

int main(int argc, const char *argv[])
{
int count = 0;
pthread_t pthread;

if(pthread_mutex_init(&lock, NULL) < 0)
{
	perror("fail to pthread_mutex_init ");
	exit(1);
}
if(pthread_create(&pthread, NULL, pthread_fun, NULL) < 0)
{
	perror("fail to pthread_create ");
	exit(1);
}
while(1)
{
	count++;
	pthread_mutex_lock(&lock);
	value_1 = count;
	value_2 = count;
	pthread_mutex_unlock(&lock);
}
pthread_join(pthread, NULL);

return 0;

}

{% endcodeblock %}
测试结果:

分析:
   假如不加互斥锁,可能出现这种情况:主进程里将count赋值给value_1和value_2,此时value_1=value_2,线程里面的if(value_1 == value_2)通过了,这时线程的时间片用完,主进程执行count++和value_1 = count,此时主进程的时间片用完,线程继续执行,打印出value_1和value_2,就会发现value_1不等于value_2。
   现在加上互斥锁,主进程count++,然后上锁,此时不能系统中断后续的count赋值给value_1和value_2,然后主线程再解锁。线程在if(value_1 == value_2)也上锁,保证后面的不被中断,打印出value_1和value_2,再解锁,整个过程,value_1一直value_2。

3.syns

{% codeblock lang:c [pthread_syns.c] https://github.com/hceng/learn/blob/master/io/day4/pthread_syns.c %}
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>

#define N 32

char buf[N] = {0};
sem_t mysem;

void *pthread_fun(void * arg)
{
while(1)
{
sem_wait(&mysem);
printf("–> %s\n",buf);
}
}

int main(int argc, const char *argv[])
{
pthread_t tid;
sem_init(&mysem, 0, 0);
if(pthread_create(&tid, NULL, pthread_fun, NULL) != 0)
{
perror(“Unable to pthread_create”);
exit(1);
}
while(1)
{
fgets(buf, 32, stdin);
sem_post(&mysem);
}
return 0;
}
{% endcodeblock %}
测试结果:

分析:
  这里利用了信号同步。主线程调用fgets等待输入,输入完成后使用sem_post发送mysem信号,线程一直调用sem_wait等待信号,一旦信号到来,就打印出buf.


进程通信

条件变量:
  定义:一个线程临界资源状态的变化,会通知另外一个线程去做相应的功能处理;条件变量跟互斥锁配合使用;
  特点:具有阻塞功能、唤醒;
为什么需要进程间通信?
  1、数据交互
  2、共享资源
  3、信号通知
  4、同步或者互斥
进程间通信方式介绍:
  1、传统的进程间通信
  无名管道、有名管道、信号通知
  2、sytem V – IPC
  共享内存、消息队列、信号量
  前面六种只能用于单台的计算机中使用
  3、套接字

1.kill

{% codeblock lang:c [kill.c] https://github.com/hceng/learn/blob/master/io/day5/kill.c %}
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, const char *argv[])
{
if(kill(atoi(argv[1]), atoi(argv[2])) < 0)
{
perror(“Unable to kill”);
exit(1);
}
return 0;
}
{% endcodeblock %}
测试结果:

分析:
  kill()有两个参数,第一个是操作进程的PID,第二个是信号种类的编号。其中2是SIGINT结束进程,所以发送这个信号后,signal就打印hello world。结束进程。

2.raise

{% codeblock lang:c [raise.c] https://github.com/hceng/learn/blob/master/io/day5/raise.c %}
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, const char *argv[])
{
printf(“hello wrold.\n”);
if(raise(atoi(argv[1])) < 0)
{
perror(“Unable to raise”);
exit(1);
}
while(1);
return 0;
}
{% endcodeblock %}
测试结果:

分析:
  raise()只能给自己发送信号,而且还不能发送SIGQUIT。这里raise自己发送SIGINT结束了自己。

3.signal

{% codeblock lang:c %}
signal(int signum, sighandler_t handler)
功能:信号响应函数
参数:

  1. signum:信号种类中的一种,跟handler关联起来,执行相应的处理方式
  2. 忽略 SIG_IGN、
    默认 SIG_DFL、
    捕捉 -->自己定义一个信号处理函数,类型跟sighandler_t的形式一致
    把linux中已经定义好的62种信号中的一种信号跟linux传递给进程的信号signo做判断,看是那种信号,再做相应的处理
    {% endcodeblock %}
    常用信号
    编号 种类 默认操作:结束进程
    2 SIGINT ctrl+c 结束进程
    3 SIGQUIT ctrl+\ 结束进程
    9 SIGKILL 结束进程
    10 SIGUSR1 结束进程
    12 SIGUSR2 结束进程
    14 SIGALRM 结束进程
    19 SIGSTOP 暂停进程
    20 SIGTSTP ctrl+z 暂停进程
    注:SIGKILL 和SIGSTOP不能被忽略或者捕捉
    {% codeblock lang:c [signal.c] https://github.com/hceng/learn/blob/master/io/day5/signal.c %}
    #include <signal.h>
    #include <stdio.h>
    //signo代表内核相当前进程发送的信号时什么,而我们又不清楚时那个信号
    //需要用if语句做判断
    void fun(int signo)
    {
    if(signo == SIGINT)
    {
    printf(“hello world \n”);
    }
    }

int main(int argc, const char *argv[])
{
//信号处理函数,本身不具有阻塞作用
//第一个参数: 要出里的信号是什么?62中的一种
//第二个参数: 三种相应方式:
//忽略(SIG_IGN)、默认(SIG_DFL)、捕捉(用户自定义函数)
signal(SIGINT, fun);
pause();
return 0;
}
{% endcodeblock %}
测试结果:

分析:
  主函数调用signal安装信号,第一个参数指定了信号类型为按下CTRL+C产生,第二个为捕捉到信号后相应操作。然后信号的处理函数判断出信号类型后执行对应的操作。
  pause()功能:引起调用进程进程阻塞,知道收到信号才会立即结束

4.alarm

{% codeblock lang:c [alarm.c] https://github.com/hceng/learn/blob/master/io/day5/alarm.c %}
#include <unistd.h>
#include <stdio.h>

int main(int argc, const char *argv[])
{
int ret = -1;
ret = alarm(9);
printf(“ret = %d \n”, ret);
sleep(3);
ret = alarm(10);
printf(“ret = %d \n”, ret);
pause();

printf("hello world \n");
//while(1);
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  先给ret赋值个-1,然后调用alarm设置为9,因为之前没有设置过alarm,所以返回0.然后休眠3秒后,有赋值为10,此时返回的为6,即上次设置后剩下的时间9-3=6.然后程序调用pause挂起程序,直到10秒后,闹钟到了,发送SIGALRM信号结束进程。

5.pipe

{% codeblock lang:c [pipe.c] https://github.com/hceng/learn/blob/master/io/day5/pipe.c %}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

int main(int argc, const char *argv[])
{
int fd[2];
pid_t pid;
char buf[1024] = {0};
int count = 0;

if(pipe(fd) < 0)
{
	perror("Unable to pipe");
	exit(1);
}

#if 1
pid = fork();
if(pid < 0)
{
perror(“Unable to fork”);
exit(1);
}

if(pid == 0)
{

// printf(“fd[0] = %d , fd[1] = %d \n”, fd[0], fd[1]);
while(1)
{
read(fd[0], buf, 32);//如果管道中没有数据,读操作会阻塞
if(strncmp(buf, “quit”, 4) == 0)
break;
printf("–> %s \n", buf);
}
}
else
{
// printf(“fd[0] = %d , fd[1] = %d \n”, fd[0], fd[1]);
while(1)
{
fgets(buf, 32, stdin);
write(fd[1], buf, strlen(buf) + 1);
if(strncmp(buf, “quit”, 4) == 0)
break;
}
wait(NULL);
}

#endif
#if 0
while(1)
{
//如果缓冲区慢,则会阻塞在write函数
write(fd[1], buf, 1024);
count++;
if(count == 64)
{
read(fd[0], buf, 1024);
read(fd[0], buf, 1024);
read(fd[0], buf, 1024);
read(fd[0], buf, 1024);
}
printf(“count = %d \n”, count);
}
#endif
close(fd[0]);
write(fd[1], buf, 1024);

return 0;

}
{% endcodeblock %}
测试结果:

分析:
  这里创建一个管道完成进程间的通信,父进程负责写数据,子进程负责读数据。
  首先是pipe()创建了一个管道,其中fd[0]是标准输入,fd[1]是标准输出。然后在fork()创建了个子进程。
  父进程的工作:不断从键盘获得数据,存入buf。然后将buf数据写入fd[1]。如果收到“quit”字符即退出,调用wait()等待子进程结束。
  子进程的工作:不断从fd[0]读取数据到buf,打印buf,如果遇到“quit”字符即退出。
  管道的意义就是,连通了fb[0]和fb[1],实现了进程间数据/信号的单向传输。

6.fifo

{% codeblock lang:c [mkfifo_w.c] https://github.com/hceng/learn/blob/master/io/day5/mkfifo_w.c %}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define N 32

int main(int argc, const char *argv[])
{
int fd_r, fd_w;
size_t bytes = 0;
char buf[N] = {0};

if(mkfifo("myfifo", 0666) < 0)
{
	perror("Unable to mkfifo");
//	exit(1);
}
fd_w = open("myfifo", O_WRONLY);
if(fd_w < 0)
{
	perror("Unable to open fd_w.");
	exit(1);
}
printf("fifo write\n");

while(1)
{
	fgets(buf, 32, stdin);
	write(fd_w, buf, strlen(buf) + 1);
	if(strncmp(buf, "quit", 4) == 0)
	break;
}

return 0;

}
{% endcodeblock %}

{% codeblock lang:c [mkfifo_r.c] https://github.com/hceng/learn/blob/master/io/day5/mkfifo_r.c %}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define N 32

int main(int argc, const char *argv[])
{
int fd_r;
size_t bytes = 0;
char buf[N] = {0};

fd_r = open("myfifo", O_RDONLY);
if(fd_r < 0)
{
	perror("Unable to open fd_r");
	exit(1);
}

while(1)
{
	read(fd_r, buf, 32);
	if(strncmp(buf, "quit", 4) == 0)
		break;
	printf("--> %s \n", buf);
}

return 0;

}
{% endcodeblock %}
测试结果:

分析:
  FIFO的功能与管道差不多,但FIFO在文件系统中拥有一个名称,其打开方式和打开普通文件是一样的,这样就可以实现非相关的进程通信。
  写入端/发送端:首先创建一个名叫“myfifo“,权限为666的FIFO。然后打开这个FIFO,不断从键盘获得数据写入FIFO里面,直到遇到”quit“退出。
  读出端/接收端:打开同名FIFO,不断从中读取数据打印出来,直到遇到”quit“退出。

7.ftok

{% codeblock lang:c [ftok.c] https://github.com/hceng/learn/blob/master/io/day6/ftok.c %}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
key_t key;

key = ftok(".", 'a');
if(key < 0)
{
	perror("Unable to ftok");
	exit(1);
}
printf("key =  %#x \n", key);
return 0;

}
{% endcodeblock %}

测试结果:

分析:
  这里ftok应该是产生一个唯一的标识,因为路径是唯一的,对应的key有是唯一的。后面的IPC对象:共享内存、消息队列、信号灯,通过key找到内核对象。通过shell命令ipcs来查看对象信息。

编程框架:
1、通过ftok函数得到key值,让不同的进程找到内核对象
2、创建或者打开内核对象(共享内存、消息队列、信号灯集)
3、操作内核对象
共享内存 :通过shmat映射内核中的共享内存到用户空间,
解除映射shmdt,不能再操作共享内存。
消息队列:通过msgsnd发送消息,msgrcv接收消息
4、删除内核对象
删除共享内存shmctl
删除消息队列msgctl

8.msg

【1】消息队列机制
{% codeblock lang:c %}
特点:
1、先进先出
2、按照类型发送、读取消息
使用ipcs -q查看系统中的消息
{% endcodeblock %}

【2】如何创建消息队列
{% codeblock lang:c %}
msgget
int msgget(key_t key, int msgflg);
功能:创建或者打开消息队列
key:让不同的进程找到同一个消息队列
msgflg:
IPC_CREAT | IPC_EXCL | 0666
创建 防止重复创建 权限
{% endcodeblock %}

【3】发送消息/接收消息
{% codeblock lang:c %}
结构体:
struct msgbuf {
long mtype; /* message type, must be > 0 /消息类型
char mtext[1]; /
message data */消息正文
};

msgsnd/msgrcv:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:
msgid: 消息队列标示符 (用于操作消息队列发送或者接收消息)
msgp:首先定义一个消息队列的结构体,类型如上所示结构体1
msgsz:消息队列正文(text)的大小
msgflg:设置为阻塞的方式0 非阻塞的方式 IPC_NOWAIT

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
msgid: 消息队列标示符 (用于操作消息队列发送或者接收消息)
msgp:首先定义一个消息队列的结构体,类型如上所示结构体1
msgsz:消息队列正文(text)的大小
msgtyp:消息的类型(必须是大于0的整数)
msgflg:设置为阻塞的方式0 非阻塞的方式 IPC_NOWAIT
{% endcodeblock %}

【4】控制(删除)消息队列msgctl
{% codeblock lang:c %}
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgid: 消息队列标示符 (用于操作消息队列发送或者接收消息)
cmd:操作消息队列的命令
IPC_STAT 获取消息队列属性信息的命令,需要定义一个struct msqid_ds结构体
{% endcodeblock %}

{% codeblock lang:c [msg_w.c] https://github.com/hceng/learn/blob/master/io/day6/msg/msg_w.c %}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>

struct msgbuf
{
long type;
char text[32];
};

#define MSG_SIZE (sizeof(struct msgbuf) - sizeof(long))

int main(int argc, const char *argv[])
{
key_t key;
int msgid;
struct msgbuf mymsg;
char buf[32] = {0};

key = ftok(".", 'a');
if(key < 0)
{
	perror("Unable to ftok");
	exit(1);
}

msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if(msgid < 0)
{
	if(errno == EEXIST)
	{
		msgid = msgget(key, 0666);
	}
	else
	{
		perror("Unable to msgget");
		exit(1);
	}
}
system("ipcs -q");

mymsg.type = 100;

//strcpy(mymsg.text,"hello world");
while(1)
{
	fgets(buf, 32, stdin);
	strcpy(mymsg.text, buf);
	
	if(msgsnd(msgid, &mymsg, MSG_SIZE, 0) < 0)
	{
		perror("Unable to msgsnd");
		exit(1);
	}
}
system("ipcs -q");

return 0;

}
{% endcodeblock %}

{% codeblock lang:c [msg_r.c] https://github.com/hceng/learn/blob/master/io/day6/msg/msg_r.c %}
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <string.h>

struct msgbuf
{
long type;
char text[32];
};

#define MSG_SIZE (sizeof(struct msgbuf) - sizeof(long))

int main(int argc, const char *argv[])
{
key_t key;
int msgid;
struct msgbuf mymsg;

key = ftok(".", 'a');
if(key < 0)
{
	perror("Unable to ftok");
	exit(1);
}

msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if(msgid < 0)
{
	if(errno == EEXIST)
	{
		msgid = msgget(key, 0666);
	}
	else
	{
		perror("Unable to msgget");
		exit(1);
	}
}
while(1)
{
	if(msgrcv(msgid, &mymsg, MSG_SIZE, 100, 0) < 0)
	{
		perror("Unable to msgsnd");
		exit(1);
	}
	system("ipcs -q");

	printf("-->%ld  %s \n", mymsg.type, mymsg.text);
}
if(msgctl(msgid, IPC_RMID, NULL) < 0)
{
	perror("Unable to msgctl");
	exit(1);
}
system("ipcs -q");
return 0;

}
{% endcodeblock %}
测试结果:

分析:
   消息队列是让进程以消息的形式交换数据。
   写入端/发送端:定义一个消息msgbuf的结构体,其中一个是类型,一个是数据。调用ftok()创建唯一的key。调用msgget()创建或打开消息队列,调用shell显示当前的ipc中的消息队列信息。设置类型为100,不断从键盘获取数据复制到mymsg.text,并调用msgsnd()发送出去。
   读出端/接收端:使用调用ftok()传入相同的参数,得到相同的key,调用msgget()打开同一个消息队列。调用msgcrv()接收消息,接收到则打印书类型和数据。后面的消息队列控制函数并没有调用到,第二个参数IPC_RMID表示立即删除队列消息。这里使用shell命令:ipcrm -q [msgid]手动删除。

9.sem

[1]信号灯机制
{% codeblock lang:c %}
信号量的集合(一个或者多个信号量,信号量是一类资源,资源的值代表了资源的数量)
实现同步:按照约定的先后顺序执行
互斥: 可以用两个信号量实现互斥操作
{% endcodeblock %}

[2]打开或者创建信号灯semget
{% codeblock lang:c %}
int semget(key_t key, int nsems, int semflg);
功能:打开或者创建信号量
参数
nsems: 信号量的数量是多少个?系统从0开始分配
举例:当nsems = 1则信号量是0
当nsems = 2则信号量分别是 0 和 1
当nsems = 3则信号量分别是 0 和 1 和 2
semflg:设置semget函数的功能:
IPC_CREAT | IPC_EXCL | 0666
创建 防止重复创建 有效权限位
返回值: 成功返回信号量操作表示符
失败 -1
{% endcodeblock %}

[3]信号灯初始化semctl
{% codeblock lang:c %}
int semctl(int semid, int semnum, int cmd, …);
功能:信号量控制函数
参数:
semnum 操作第几个信号量
举例 semnum 是 0,表示操作第0个信号量
举例 semnum 是 1,表示操作第1个信号量
cmd: 对信号量使用的命令
IPC_STAT 获取信号量的属性信息
SETVAL 初始化信号量的值,需要定义一个联合体
类型如下所示:
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_STAT、SETVAL命令,必须使用第四个参数定义一个联合体变量来接收
如果定义IPC_RMID则不用使用第四个参数
{% endcodeblock %}

[4]信号量操作方式
{% codeblock lang:c %}
int semop(int semid, struct sembuf sops, unsigned nsops);
功能:信号量的pv操作
sops:对信号量的操作方式
unsigned short sem_num; /
第几个信号量*/
short sem_op; /* 信号量的PV操作,正数表示释放操作、负数表示申请操作 */
short sem_flg; /0表示阻塞方式,IPC_NOWAIT表示非阻塞方式/
nsops:表示操作几个信号量,从0开始
P申请操作
V释放操作
{% endcodeblock %}

{% codeblock lang:c [sem_w.c] https://github.com/hceng/learn/blob/master/io/day6/sem_w.c%}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/shm.h>

union semun
{
int val;
};

int main(int argc, const char *argv[])
{
key_t key;
int semid;
union semun mysem;
struct sembuf mybuf;
char buf1[32] = {0};
key = ftok(".", ‘a’);
if(key < 0)
{
perror(“fail to ftok”);
exit(1);
}
semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if(semid < 0)
{
if(errno == EEXIST)
semid = semget(key, 2, 0666);
else
{
perror(“fail to semget”);
exit(1);
}
}
else
{
mysem.val = 0;
semctl(semid, 0, SETVAL, mysem);
mysem.val = 1;
semctl(semid, 1, SETVAL, mysem);
}

int shmid = shmget(key, 128, IPC_CREAT | 0666);
if(shmid < 0)
{
	if(errno == EEXIST)
	{
		shmid = shmget(key, 128, 0666);
	}
	else{
		perror("fail to shmget");
		exit(1);
	}
}
char *buf = (char *)shmat(shmid, NULL, 0);
if(buf == (char *)-1)
{
	perror("fail to shmat");
	exit(1);
}
while(1)
{

	fgets(buf1, 32, stdin);

	mybuf.sem_num = 1;
	mybuf.sem_op = -1;
	mybuf.sem_flg = 0;
	semop(semid, &mybuf, 1);
	strcpy(buf,buf1);
	mybuf.sem_num = 0;
	mybuf.sem_op = 1;
	mybuf.sem_flg = IPC_NOWAIT;
	semop(semid, &mybuf, 1);
}
system("ipcs -s");
return 0;

}
{% endcodeblock %}

{% codeblock lang:c [sem_rw.c] https://github.com/hceng/learn/blob/master/io/day6/sem_r.c%}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/sem.h>

union semun
{
int val;
};

int main(int argc, const char *argv[])
{
key_t key;
int semid;
union semun mysem;
struct sembuf mybuf;

key = ftok(".", 'a');
if(key < 0)
{
	perror("fail to ftok");
	exit(1);
}
semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if(semid < 0)
{
	if(errno == EEXIST)
		semid = semget(key, 2, 0666);
	else
	{
		perror("fail to semget");
		exit(1);
	}
}
else
{
	mysem.val = 0;
	semctl(semid, 0, SETVAL, mysem);
	mysem.val = 1;
	semctl(semid, 1, SETVAL, mysem);
}
int shmid = shmget(key, 128, IPC_CREAT | 0666);
if(shmid < 0)
{
	if(errno == EEXIST)
	{
		shmid = shmget(key, 128, 0666);
	}
	else{
		perror("fail to shmget");
		exit(1);
	}
}
char *buf = (char *)shmat(shmid, NULL, 0);
if(buf == (char *)-1)
{
	perror("fail to shmat");
	exit(1);
}
while(1)
{
	mybuf.sem_num = 0;
	mybuf.sem_op = -1;
	mybuf.sem_flg = 0;
	semop(semid, &mybuf, 1);
	printf("--> %s \n", buf);
	mybuf.sem_num = 1;
	mybuf.sem_op = 1;
	mybuf.sem_flg = IPC_NOWAIT;
	semop(semid, &mybuf, 1);
}
system("ipcs -s");
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  信号量不是用来在进程间传输数据的,而是用来同步数据的。这里里共享内存为例,完成两个进程的同步。
  写入端/发送端:首先调用ftok()创建唯一的key。调用semget ()创建或打开信号量集,其中第二个参数表示创建两个信号量。然后调用控制函数semctl()分别设置两个的值为0和1。然后创建一个共享内存,映射到用户空间,不断从键盘获得输入。mybuf.sem_num表示设置第1个信号量,mybuf.sem_op为负表示期望减,mybuf.sem_flg为0为阻塞,IPC_NOWAIT为非阻塞。然后在调用semop()进行设置刚才初始化的sembuf。即读去到数据则欲将第一个信号量减1。复制好后,再将第0个信号量欲加1.
  读出端/接收端:前面部分都是对应的,在循环部分,先欲把第0个信号量减1,等打印完后,再欲把第1个信号量设置为1。
  即整个流程,先信号量0和1分别为0和1。sem.w收到数据,则欲将第1个信号量减1,成功,继续执行执行strcpy(),然后将信号量0设置为1。这时sem.r中,之前欲将第0个参数减1,但为0,减不动,阻塞着,现在发现第0个信号量被设置为1,于是继续执行,打印了出来。然后设置第1个参数为1。这时,sem.w中刚走完一个循环,且收到了键盘数据,但现在第1个信号量是0,无法拷贝,现在发现第1个参数被设置为了1,于是又继续执行后续的拷贝和设置信号量。如此往复。

10.shm

  • 共享内存:内核空间中的一块区域,由用户创建,系统维护里面的数据结构,通过获取到的描述符shmget的返回值,对共享内存进行操作;
  • 特点
    1、高效,因为用户可以直接对内和对象进行操作,并不需要把数据在写到自己的用户空间里,在所有的进程间通信对象中是最快的。
  • 注意
    1、映射,共享内存的映射,就是把共享内存段的地址传递给用户空间相应数据类型的指针变量。
    2、同步和互斥:对共享内存进行循环操作的时候,容易出现一个进程在写操作的时候,另一个已经打读了好多次,为了能够完成写一次读一次操作,需要进行同步或者互斥。
  • 步骤
    **【1】共享内存创建 **
    {% codeblock lang:c %}
    shmget
    int shmget(key_t key, size_t size, int shmflg);
    功能: 创建或者打开共享内存
    key: 通过ftok函数返回,用于让不同的进程找到内核区域中的内核对象(共享内存)
    size:指定共享内存的大小,以字节为单位
    shmflg:指定创建的共享内存具有什么权限
    IPC_CREAT | IPC_EXCL | 0666
    {% endcodeblock %}

【2】映射共享内存
{% codeblock lang:c %}
shmat
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:把内核空间的共享内存映射到用户空间,(因为用户空间根内核空间不能直接访问,所以
通过映射的方式,简介得到这块共享内存地址)
shmid:【操作】共享内存的表示符
shmaddr: NULL有系统来分配一块共享内存
shmflg: 0
{% endcodeblock %}

【3】解除映射
{% codeblock lang:c %}
shmdt
{% endcodeblock %}

【4】删除共享内存
{% codeblock lang:c %}
shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:共享内存对象控制函数
通过命令,也就是第二个参数设置:
IPC_STAT用来获取共享内存的一些属性信息
IPC_RMID用来删除共享内存对象

buf:用来存储共享内存的属性信息的,需要定义一个struct  shmid_ds 的结构体

{% endcodeblock %}

{% codeblock lang:c [shm_w.c] https://github.com/hceng/learn/blob/master/io/day6/shm_w.c %}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <string.h>
#include <errno.h>

int main(int argc, const char *argv[])
{
key_t key;
int shmid;
char * ptr;

key = ftok(".", 'a');
if(key < 0)
{
	perror("Unable to ftok");
	exit(1);
}
printf("key =  %#x \n", key);

shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if(shmid < 0)
{
	if(errno == EEXIST)
	{
		shmid = shmget(key, 128, 0666);
	}
	else
	{
		perror("Unable to shmget");
		exit(1);
	}
}
if((ptr = shmat(shmid, NULL, 0)) == (char *)-1)
{
	perror("Unable to shmat");
	exit(1);
}
strcpy(ptr, "hello world");
if(shmdt(ptr) < 0)
{
	perror("Unable to shmdt");
	exit(1);
}

#if 0
if(shmctl(shmid, IPC_RMID, NULL) < 0)
{
perror(“Unable to shmctl”);
exit(1);
}
#endif

return 0;

}
{% endcodeblock %}

{% codeblock lang:c [shm_r.c] https://github.com/hceng/learn/blob/master/io/day6/shm_r.c %}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <string.h>
#include <errno.h>

int main(int argc, const char *argv[])
{
key_t key;
int shmid;
char * ptr;

key = ftok(".", 'a');
if(key < 0)
{
	perror("Unable to ftok");
	exit(1);
}

shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if(shmid < 0)
{
	if(errno == EEXIST)
	{
		shmid = shmget(key, 128, 0666);
	}
	else
	{
		perror("Unable to shmget");
		exit(1);
	}
}
if((ptr = shmat(shmid, NULL, 0)) == (char *)-1)
{
	perror("Unable to shmat");
	exit(1);
}
printf("--> %s \n",ptr);
if(shmdt(ptr) < 0)
{
	perror("Unable to shmdt");
	exit(1);
}
if(shmctl(shmid, IPC_RMID, NULL) < 0)
{
	perror("Unable to shmctl");
	exit(1);
}
return 0;

}
{% endcodeblock %}
测试结果:

分析:
  写入端写入数据到共享内存,读取端读出并释放。
  写入端/发送端:首先调用ftok()创建一个key,调用shmget()分配一个所需共享内存,得到shmid。然后调用shmat()再将内核中的共享内存映射到用户空间,最后调用strcpy()将“hello world“写入到这个空间,并释放用户空间。
  读出端/接收端:使用调用ftok()传入相同的参数,得到相同的key,调用shmget也得到同样的shmid,再调用shmat()映射,打印出之前的数据。最后
  调用shmdt()和shmctl()释放用户空间和内核空间。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值