应用层:写一个QT可执行程序、一个C程序
驱动层:写一个LED、蜂鸣器、pwm驱动
硬件层:焊接、layout
Linux系统介于应用层和驱动层之间,Linux系统会向应用层提供接口,学习使用的基本是Linux内核向用户提供的接口或者可以说是函数
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
//argc: 命令行中参数的个数 文件名也算参数
//argv: 命令行中的参数
printf("argc is %d\n",argc);
for(int i=0;i<argc;i++)
{
printf("argv is %s\n",argv[i]);
}
return 0;
}
标准IO和IO文件
文件IO
: 文件lO就是直接调用内核提供的系统调用函数。
标准IO
: 标准IO就是间接调用系统调用函数,是C库函数。
两者区别
文件lQ
是直接调用内核提供的系统调用函数,头文件是unistd.h,标准lO是间接调用系统调用函数,头文件是stdio.h,文件lO是依赖于Linux操作系统的。文件IO围绕着 文件操作符 操作的
标准IO
是不依赖操作系统的,所以在任何的操作系统下,使用标准IO,也就是C库函数操作文件的方法都是相同的。标准IO围绕着 流 操作
man 2 open 查看手册
1、open函数
文件描述符
对于文件IO来说,一切都是围绕文件描述符
来进行的。在Linux系统中,所有打开的文件都有一个对应的文件描述符。文件描述符的本质是一个非负整数,当我们打开一个文件时,系统会给我们分配一个文件描述符。
当我们对一个文件做读写操作的时候,我们使用open函数返回的这个文件描述符会标识该文件,并将其作为参数传递给read或者write函数。
在posix.1应用程序里面,文件描述符0,1,2分别对应着标准输入,标准输出,标准出错。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname,int flags); //失败返回-1,成功返回文件标识符, flags可以多个一起用,
int open(const char *pathname,int flags, mode_t mode); //flags用到了O_CREAT,就要用第三个参数(文件权限)
Flags
The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening
the file read-only, write-only, or read/write, respectively.
In addition, zero or more file creation flags and file status flags can be bitwise-or'd in flags. The file creation
flags are O_CLOEXEC, O_CREAT, O_DIRECTORY, O_EXCL, O_NOCTTY, O_NOFOLLOW, O_TMPFILE, and O_TRUNC. The file status
flags are all of the remaining flags listed below. The distinction between these two groups of flags is that the file
creation flags affect the semantics of the open operation itself, while the file status flags affect the semantics of
subsequent I/O operations. The file status flags can be retrieved and (in some cases) modified; see fcntl(2) for de‐
tails.
访问权限用数字表示
可执行-1
可写-2
可读-4
可读可写就是和6,没有权限0
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
//argc: 命令行中参数的个数 文件名也算参数
//argv:命令行中的参数
int fd;
fd=open("open_a.c",O_CREAT|O_RDWR,0666);
if(fd<0)
{
printf("open is error\n");
}
printf("fd is %d\n",fd);
return 0;
}
//fd is 3
-rw-rw-r--
- : 0
rw- : r=4 w=2 -=0 ,所以最后=6
rw- 6
r-- 4
所以是0664
umask=0002
mode_t mode=0666
这里的值等于 mode_t mode & (~umask)
2、close函数
#include <unistd.h>
close(fd);
文件描述符是有限的,一般1024个,所以一般用完就关闭文件描述符
3、read函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
//返回 long int 读取到的字节数
//buf: 存放数据
//count: 要读取的字节数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
int fd;
fd=open("open_a.c",O_RDWR);
char buf[32]={0};
ssize_t ret;
ret=read(fd,buf,32);
if(ret<0){
printf("read is error");
return -2;
}
printf("buf is %s\n",buf);
printf("ret is %ld\n",ret);
ret=read(fd,buf,32);
printf("ret is %ld\n",ret);
close(fd);
return 0;
}
//buf is helloword!
//ret is 11
//ret is 0
4、write函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd
int main(int argc, char* argv[])
{
int fd;
char buf[32]={0};
ssize_t ret;
fd=open("open_a.c",O_RDWR);
write(fd,"hello\n",6);
return 0;
}
5、综合练习
通过命令行,把 a.c 文件的内容写到b.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
//步骤一:判断命令行中的参数
if(argc!=3){
printf("Usage:%s <src file><obj file>\n",argv[0]);
return -1;
}
//步骤一:定义变量
int fd_src,fd_obj;
char buf[32]={0};
ssize_t ret;
//步骤三:打开文件获得文件描述符
fd_src=open(argv[1],O_RDWR);
fd_obj=open(argv[2],O_RDWR|O_CREAT,0666);
//步骤四:读写操作
while((ret=read(fd_src,buf,32))!=0)
{
write(fd_obj,buf,ret);
}
close(fd_src);
close(fd_obj);
return 0;
}
6、lseek函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
fd:文件描述符
off_t offset:相对于当前位置的偏移
whence:当前位置 。SEEK_SET:文件开头 SEEK_CUR:文件读写指针位置 EEK_END:文件末尾
returns: the resulting offset location as measured in bytes from the beginning of the file:基于此还可以确定文件长度
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
int fd;
fd=open("open_a.c",O_RDWR);
if(fd<0)
{
printf("open is error\n");
return -1;
}
char buf[32]={0};
ssize_t ret;
ret=read(fd,buf,32);
if(ret<0){
printf("read is error");
return -2;
}
printf("buf is %s",buf);
printf("ret is %ld\n",ret);
lseek(fd,0,SEEK_SET);
ret=read(fd,buf,32);
printf("buf is %s",buf);
printf("ret is %ld\n",ret);
close(fd);
return 0;
}
目录IO
围绕着目录进行操作
mkdir 创建目录
opendir 打开目录
readdir 读取目录
closedir 关闭目录
查询手册 man 3 函数
1、mkdir 函数
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
pathname: 路径名
mode: 权限
return: zero on success, or -1 if an error
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
int ret;
if(argc!=2)
{
printf("usage %s <name file>\n",argv[0]);
return -1;
}
ret=mkdir(argv[1],0666);
if(ret<0)
{
printf("mkdir is error\n");
return -2;
}
printf("mkdir is ok\n");
return 0;
}
2、opendir 和closedir 函数
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
name:地址
return: 成功目录流指针,失败NULL。和文件标识符类似
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
returns 0 on success. On error, -1
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
int ret;
DIR*dp;
if(argc!=2)
{
printf("usage %s <name file>\n",argv[0]);
return -1;
}
dp=opendir(argv[1]);
if(dp!=NULL)
{
printf("opendir is ok\n");
}
closedir(dp);
return 0;
}
3、readdir 函数
可以读取目录下所有文件包含目录和文件
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
Return:On success, readdir() returns a pointer to a dirent structure. (This structure may be statically allocated; do not attempt to free(3) it.)
struct dirent {
ino_t d_ino; /* Inode number 文件的ID号*/
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
int ret;
DIR*dp;
struct dirent*dir;
if(argc!=2)
{
printf("usage %s <name file>\n",argv[0]);
return -1;
}
dp=opendir(argv[1]);
if(dp!=NULL)
{
printf("opendir is ok\n");
}
//循环读取
while(1)
{
dir=readdir(dp);
if(dir!=NULL){
printf("filename is %s\n",dir->d_name);
}else{
break;
}
}
closedir(dp);
return 0;
}
4、综合练习
通过命令行,把 a.c 文件的内容写到b.c
在此基础上,
1、打印拷贝的目录下所有文件名,拷贝需要的文件
2、通过键盘输入要拷贝的文件路径和文件名等信息
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[])
{
int fd_src,fd_obj;
char buf[32]={0};
char file_path[32]={0};
char file_name[32]={0};
ssize_t ret;
DIR*dp;
struct dirent *dir;
printf("please enter the file path\n");
scanf("%s",file_path);
//打开目录,打印文件
dp=opendir(file_path);
if(dp==NULL){printf("open is error");return -1;}
printf("open is ok\n");
while(1){
dir=readdir(dp);
if(dir!=NULL){
printf("filename is %s\n",dir->d_name);
}else
break;
}
//拷贝文件
printf("please enter the source file name\n");
scanf("%s",file_name);
char *p=(strcat(strcat(file_path,"/"),file_name)); //查看输入路径有无错误
printf("%s\n",p);
fd_src=open(strcat(strcat(file_path,"/"),file_name),O_RDWR);
//fd_src=open(file_name,O_RDWR);
if(fd_src<0)
{
printf("open is error\n");
}
printf("please enter the dest file name\n");
scanf("%s",file_name);
fd_obj=open(file_name, O_RDWR|O_CREAT,0666);
if(fd_obj<0)
{
printf("open fd_obj is error\n");
}
//步骤四:读写操作
while((ret=read(fd_src,buf,32))!=0)
{
write(fd_obj,buf,ret);
}
closedir(dp);
close(fd_src);
close(fd_obj);
return 0;
}
库
1、定义
- 一种可执行的二进制文件,编译好的代码。
- 使用库可以提高开发效率
- Linux库分为静态库和动态库
- 静态库:静态库在程序编译的时候会被链接到目标代码里面。所以我们的程序运行就不再需要该静态库了。因此编译出来的体积就比较大,静态库以
lib
开头.a
结尾(10个程序都需要某个静态库,编译的时候就需要链接10次,不太方便) - 动态库: 动态库(也叫共享库)在程序编译的时候不会被链接到目标代码里面,所以我们的程序运行就需要该动态库。因此编译出来的体积就比较小。静态库以
lib
开头.so
结尾(如果程序需要某个动态库,那移植程序的时候不太方便,因为需要环境里面有这个库) - 两者最大的区别就是库的加载时间不同,静态库的函数在程序编译时会被拷贝过来,而动态库不是在编译阶段是在程序运行时载入的
- 静态库:静态库在程序编译的时候会被链接到目标代码里面。所以我们的程序运行就不再需要该静态库了。因此编译出来的体积就比较大,静态库以
2、静态库的制作和使用
- 编写或准备库的源代码
mylib.c
#include <stdio.h>
void mylib(void);
void mylib(void)
{
printf("this is mylib\n");
}
test.c
#include <stdio.h>
void mylib(void);
int main(void)
{
mylib();
return 0;
}
- 将源码.c文件编译生成.o文件
gcc -c mylib.c 编译成.o
- 使用ar命令创建静态库
ar cr libmylib.a mylib.o
c:create r:replace 覆盖
libmylib.a :库文件名
mylib :库名
- 测试库文件
gcc test.c -lmylib -L. -o test
-l库名
-L库文件路径
3、动态库的制作和使用
- 编写或准备库的源代码
- 将源码.c文件编译生成.o文件
gcc -c -fpic mylib.c -fpic产生位置无关代码,因为动态库可以被任何位置代码调用
- 使用gcc命令创建动态库
gcc -shared -o libmylib.so mylib.o -shared生成动态库
- 测试库文件
在运行程序前需要配置动态库路径
如果我们的程序代码用到了库文件里面的函数,我们在编译的时候需要链接库。系统默认会在/lib或者/usr/lib去找库文件。或者在编译的时候我们制定库的路径。|
第一种方法:
将生成的动态库拷贝到lib或者/usr/lib里面去,因为系统会默认去这俩个路径下寻找。(自己写的库不建议拷贝过去)
第二种方法:
把我们的动态库所在的路径加到环境变量里面去,比如我们动态库所在的路径为/home/test,我们就可以这样添加。(临时的)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/test动态库所在路径
第三种方法:
修改ubuntu下的配置文件/etc/ld.so.conf
,我们在这个配置文件里面加入动态库所在的位置,然后使用命令Idconfig
更新目录。(root用户)
然后执行代码
gcc -test.c -lmylib -L. -o test
./test
进程
1、进程定义、进程ID,进程通信,进程的状态
程序是保存在存储介质上的经过编译的可执行二进制文件,是静态的。
进程指的是正在运行的程序
打开任务管理器可以看到后台有很多进程
那一个CPU如何处理这么多进程-不断切换
进程ID
:每个进程都有唯一的标识符,简称PID
进程之间不能通过应用层直接交流通信,需要通过Linux内核,在里面创造一个对象(特殊的文件),然后交流。
进程间通信的方法:
管道通信:有名管道,无名管道
信号通信:信号的发送,信号的接受,信号的处理
IPC通信:共享内存,消息队列,信号灯
Socket通信:一个网络中的两个进程通信
2、进程控制
(1)fork函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void); //create a child process
On success, the PID of the child process is returned in the parent, and 0 is returned in the child.
On failure, -1 is returned in the parent, no child process is created
返回值: fork函数有三种返回值,在父进程中,fork返回新创建的子进程的PID,在子进程中,fork返回0,如果出现错误,fork返回一个负值。
getpid() 获得当前进程的pid
getppid() 获得当前进程的父进程的pid
#include<stdio.h>
#include<unistd.h>
int main(void)
{
pid_t pid;
pid=fork();
if(pid<0)
{
printf("fork is error");
return -1;
}
//父进程
if(pid>0)
{
printf("This is parent pid %d\n",getpid());
}
//子进程
if(pid==0)
{
printf("this is child pid %d, parent pid %d\n",getpid(),getppid());
}
return 0;
}
This is parent pid 13032
This is child pid 13033, parent pid 13032
子进程是父进程的拷贝,父进程的执行是从头开始的,子进程的执行是从fork()
开始的
父子进程执行的顺序不一定
(2)exec函数族
利用exex可以实现子进程完成不同的程序, 理解为换核不换壳
:不执行原来复制的父进程的程序,而是执行execl里的执行文件
在Linux中并没有exec函数,而是有6个以exec开头的函数族,下面列举了exec函数族的6个函数成员的函数原型。
int execl(const char *path, const char *arg,...) 最常用的
int execv(const char *path, char *const argv[)
int execle(const char *path, const char *arg, .... char *const envp[])
int execve(const char *path, char *const argv[, char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, char *const argv[)
在Linux中使用exec函数族主要有以下两种情况:
1.当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec函数族让自己重生。
2.如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生。
int execl(const char *path, const char *arg,...)
const char *path:可执行文件的完整路径
const char *arg:可执行文件名,可执行文件的参数,NULL结尾
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
pid_t pid;
pid=fork();
if(pid<0)
{
printf("fork is error");
return -1;
}
//父进程
if(pid>0)
{
printf("This is parent pid %d\n",getpid());
}
//子进程
if(pid==0)
{
printf("this is child pid %d,parent pid %d\n",getpid(),getppid());
execl("/home/yilp/Linux_pro/进程/hello","hello",NULL);
exit(1);//不会执行后面的程序了
}
return 0;
}
利用子进程执行shell命令 ls -a
exit(x) 函数 退出进程,x=0表示进程执行成功后退出,1表示失败后退出
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
pid_t pid;
pid=fork();
if(pid<0)
{
printf("fork is error");
return -1;
}
//父进程
if(pid>0)
{
printf("This is parent pid %d\n",getpid());
}
//子进程
if(pid==0)
{
printf("this is child pid %d,parent pid %d\n",getpid(),getppid());
//execl("/home/yilp/Linux_pro/进程/hello","hello",NULL);
execl("/bin/ls","ls","-a",NULL);
exit(1);//子进程执行成功后不会执行后面的程序了
}
return 0;
}
//打印
//This is parent pid 13352
//this is child pid 13353,parent pid 1
//yilp@yilp-virtual-machine:~/Linux_pro/进程$ . .. execl execl.c fork fork.c hello.c
(3)ps 和 kill 命令
ps命令
ps命令可以列出系统中当前运行的那些进程。命令格式: ps [参数]
命令功能:用来显示当前进程的状态
常用参数:a、u 、x
a:显示进程执行的可执行文件
u:显示进程归属的用户 和 相关内存使用情况
x:显示不关联终端的进程
kill命令
kill命令用来杀死进程举例: kill -9(SIGKILL) PID
SIGKILL这个信号由9表示
有以下kill 信号
sudo kill -9 pid
(4)孤儿进程和僵尸进程
孤儿进程:
父进程结束以后,子进程还未结束,这个子进程就叫做孤儿进程。孤儿进程会被系统的init
进程也就是(PID号=1,也不一定是1)的进程领养,init
进程也就变成了孤儿进程的父进程。
僵尸进程:
子进程结束以后,父进程还在运行,但是父进程不去释放进程控制块(子进程结束后,需要父进程去释放资源,这也是为什么孤儿进程会被领养,因为要释放资源).这个子进程就叫做僵尸进程,僵尸进程是死掉的进程,状态为Z+。
僵尸进程如果父进程挂掉了,就变成了孤儿进程,然后被领养
(5)wait 函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus); //阻塞当前进程,分析当前进程的某个子进程是不是已经退出了,如果有就会去回收释放这个子进程,如果没有找到就会一直阻塞在这里
与wait函数 有关的两个宏定义
WIFEXITED(wstatus): 子进程正常退出,宏定义为真
WEXITSTATUS(wstatus): 正常退出,宏定义的值为子进程的退出值
return:回收的子进程pid,失败-1
3、守护进程
定义
:守护进程是运行在后台,不和任何控制终端关联
创建
:必须作为init
进程的子进程;不和终端交互
- 1、使用
fork
函数创建子进程,然后父进程使用 exit 进程 退出 - 2、调用
setsid
函数使得子进程抛弃原来拷贝父进程的会话进程组和控制终端等的控制,新建一个(就是新的会话的首进程,新的进程组里的唯一进程,而且没有终端),不和终端交互
- 3、调用chdir函数,将当前的工作目录改成根目录,增强程序的健壮性(不是必要的)
- 4、重设我们umask文件掩码,增强程序的健壮性(因为子进程复制了父进程的掩码,不是必要的)
- 5、关闭文件描述符(0 标准输出,1 标准输入,2 标准出错,守护进程不使用终端了,用不着),节省资源 (不是必要的)
- 6、执行我们需要执行的代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(void)
{
//1、创建子进程
pid_t pid;
pid=fork();
//退出父进程
if(pid>0)
{
exit(0);
}
if(pid==0)
{
//2、新建会话
setsid();
//3、更改工作目录
chdir("/");
//4、重设umask umask() 会将调用进程的文件mode创建为掩码(umask) & mode
umask(0);
//5、关闭文件描述符
close(0);
close(1);
close(2);
//6、子进程需要执行的代码
while(1){
}
}
return 0;
}
可以看到守护进程在后台运行
4、进程通信
(1)管道通信
a. 无名管道
无名管道:在文件系统中不存在这样一个文件,只能实现有关系的进程间通信,要在`fork`函数创建子进程前创建,确保操作同一个管道
#include <unistd.h>
int pipe(int pipefd[2]);
int pipefd[2] 不是传入管道的文件描述符,而是返回了读写两个端
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main(void)
{
int fd[2];
char buf[32]={0};
pid_t pid;
pipe(fd);
printf("fd[0]is %d\n",fd[0]);//3
printf("fd[1]is %d\n",fd[1]);//4
pid=fork();
if(pid>0)
{
int status;
close(fd[0]);
write(fd[1],"hello",5);
close(fd[1]);
wait(&status);
exit(0);
}
if(pid==0)
{
close(fd[1]);//子进程不写,只读,关闭写描述符
read(fd[0],buf,32); //
printf("buf is %s\n",buf);
close(fd[0]);
exit(0);
}
return 0;
}
//fd[0]is 3
//fd[1]is 4
//buf is hello
b. 有名管道
在文件系统中存在这样一个文件,可以让互不相干的进程进行通信。特殊的文件,不能用create创建,用mkfifo命令或者函数创建
然后再用 open 和 read 函数操作这个文件就可以了。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode); // 权限mode(mode & ~umask).
返回值:成功0;失败-1
写数据的进程
int main(int argc,char*argv[])
{
int fd;
int ret;
if(argc<2){
printf("usage:%s<file name>",argv[0]);
return -1;
}
// access()函数 判断文件是否存在
if(access(argv[1],F_OK)==-1){
ret=mkfifo(argv[1],0666);
return -2;
}
fd=open(argv[1],O_WRONLY);
while(1){
sleep(1);
write(fd,"hello",5);
}
close(fd);
return 0;
}
读数据的进程
int main(int argc,char*argv[])
{
int fd;
int ret;
char buf[32]={0};
if(argc<2){
printf("usage:%s<file name>",argv[0]);
return -1;
}
if(access(argv[1],F_OK)==-1){
ret=mkfifo(argv[1],0666);
return -2;
}
fd=open(argv[1],O_RDONLY);
while(1){
sleep(1);
read(fd,buf,32);
printf("buf is %s\n",buf);
memset(buf,0,sizeof(buf));//用0填充buf指针内存的前32个字节
}
close(fd);
return 0;
}
//void *memset(void *s, int c, size_t n);
//The memset() function fills the first n bytes of the memory area pointed to by s with the constant byte c.
//用常数字节 c 填充 指针 s 指向的内存区域的前 n 个字节。
(2)信号通信:发送、接收、处理
系统里有各种信号,比如按键、硬件变化
a. 信号发送
kill函数:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid int sig);
raise函数:
#include <signal.h>
int raise(int sig); raise函数等价于kill(getpid(),sig); 给自己发信号
alarm函数:
#include <unistd.h>
unsigned int alarm(unsigned int seconds); 类似于定时器,时间到了就关闭该进程
b. 信号的接收
进程要接收信号,那么进程就不能结束停止
有 while sleep(睡眠时间内不会结束) pause 三种方法
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
#include <unistd.h>
int pause(void); 令目前进程暂停进入睡眠状态,知道被信号(signa)所打断
返回值:-1
d. 信号的处理
有系统默认(终止),忽略,捕获(收到这个信号,去执行一些动作)三种选择
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
简化:
signal(参数1,参数2);
参数1:我们要进行处理的信号,系统的信号我们可以在终端输入 kill -l 查看。
参数2:处理的方式(是系统默认、忽略、捕获)。
-
signal(SIGINT ,SIG_IGN)
SIG_IGN
代表忽略SIGINT
信号
-
signal(SIGINT ,SIG_DFL)
SIG_DFL
代表执行系统默认操作,大多数信号的系统默认动作时终止该进程。
-
signal(SIGINT ,myfun)
捕捉SIGINT
这个信号,然后执行myfun
函数里面的代码,myfun
由我们自己定义。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void myfun(int sig)
{
if(sig==SIGINT){
printf("get signal\n");
}
}
int main(void)
{
signal(SIGINT,SIG_IGN);//忽略ctrl+c
//signal(SIGINT,SIG_DEF);//默认会终止
signal(SIGINT,myfun);//执行myfun
while(1){
printf("wait signal\n");
sleep(1);
}
return 1;
}
忽略:此时ctrl+c
终止不了程序,kill -2 该程序pid 也终止不了,但其他信号可以
捕获
(3)共享内存通信
a. shmget创建
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg); //allocates a System V shared memory segment
key_t key : IPC_PRIVATE 宏或者 ftok 函数的返回值 ipcs -m 查看当前有的共享内存段 ipcrm -m key 删除
size_t size : 共享内存的大小
int shmflg : 权限
return : 成功返回共享内存的标识符,失败返回-1
ftok 生成key值
//ftok - convert a pathname and a project identifier to a System V IPC key
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
使用 IPC_PRIVATE宏 来创建key值 ,key=0
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
int main(void)
{
int shmid;
shmid=shmget(IPC_PRIVATE,1024,0777);
if (shmid<0){
printf("shmget is error\n");
return -1;
}
printf("shmid is %d\n",shmid);
return 0;
}
使用 ftok 来创建key值, key!=0
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
int main(void)
{
int shmid;
key_t key=ftok("./memory.c",'m');
shmid=shmget(key,1024,0777|IPC_CREAT); //使用ftok 权限要加上IPC_CREAT
if (shmid<0){
printf("shmget is error\n");
return -1;
}
printf("shmid is %d\n",shmid);
return 0;
}
b. shmat创建共享内存映射
用户操控时,利用 shmat
函数将共享内存映射到进程中,这样就不用进入内核了,效率更高
#include <sys/types.h>
#include <sys/shm.h>函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
int shmid:共享内存的标识符,也就是shmget函数的返回值
const void *shmaddr:映射到的地址,一般写NULL,NULL为系统自动帮完成映射
int shmflg:通常为0,表示共享内存可读可写,或者为SHM_RDONLY,表示共享内存只读
return:成功返回共享内存映射到进程中的地址,失败返回-1
c. shmdt删除地址映射
shmdt 将进程中的地址映射删除。当一个进程不需要共享内存的时候,使用这个函数将他从进程地址空间中脱离,并不会删除内核里面的共享内存对象。
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数: const void *shmaddr:共享内存映射后的地址
返回值:成功返回0,失败返回-1
d. shmctl 删除操共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid,int cmd, struct shmid_ds *buf);
参数:
int shmid:要操作的共享内存的标识符
int cmd: IPC_STAT(获取对象属性)IPC_SET(设置对象属性)IPC_RMID(删除对象)
struct shmid_ds *buf:当cmd指定 IPC_STAT / IPC_SET,用来保存或者设置的属性。
d. 基于共享内存实现线程间的通信
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
int shmid;//共享内存id
key_t key;//共享内存key值
pid_t pid; //进程id
char*s_addr,*p_addr;
key=ftok("./memory2.c",'m');
shmid=shmget(key,1024,0777|IPC_CREAT);
if (shmid<0){
printf("shmget is error\n");
return -1;
}
printf("shmid is %d\n",shmid);
pid=fork();
if(pid>0){
p_addr=shmat(shmid,NULL,0);//共享内存映射
strncpy(p_addr,"hello",5);//向映射的地址写内容
wait(NULL);
exit(0);
}
if(pid==0){
sleep(2);
s_addr=shmat(shmid,NULL,0);
printf("s_addr is %s\n",s_addr);
exit(0);
}
return 0;
}
(4)消息队列通信
和共享内存通信类似
a. msgget 创建
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg); get a System V message queue identifier
参数:
key_t key: 和消息队列相关的key值 IPC_PRIVATE 宏或者 ftok 函数
int msgflg: 访问权限
因为消息队列是一个链式队列,可以不断插入,因此,不用指定大小
返回值:成功返回消息队列的ID,失败-1
ipcs -q 查看当前系统里消息队列
b. msgctl 删除
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
int msqid:消息队列的ID
int cmd: IPC_STAT: 读取消息队列的属性,然后把它保存在buf指向的缓冲区。
IPC_SET: 设置消息队列的属性,这个值取自buf参数
IPC_RMID:删除消息队列
struct msqid_ds *buf: 消息队列的缓冲区
返回值:成功返回0,失败返回-1
c. msgsnd发送
int msgsnd(int msqid,const void *msgp, size_t msgsz, intmsgflg)
参数:
int msqid: 消息队列ID
const void *msgp: 指向消息类型的指针
size_t msgsz: 发送的消息(正文,不是整个结构体)的字节数。
int msgflg: 如果为0,直到发送完成函数才返回,即阻塞发送,IPC_NOWAIT:消息没有发送完成,函数也会返回,即非阻塞发送
返回值:成功返回0,失败返回-1
消息结构体
struct msgbuf {
long mtype;消息的类型
char mtext[1];消息的内容
};
d. msgsrcv接收
ssize_t msgrcv(int msqid, void (msgp, size_t msgsz, longmsgtyp, int msgflg);
参数:
int msqid:消息队列ID,
void *msgp:接收消息的缓冲区
size_t msgsz:想要接收消息的字节数
long msgtyp:接收消息的标识 0就代表接收消息队列里的第一个消息
int msgflg:0表示阻塞读取,IPC_NOWAIT表示非阻塞读取返回值,成功返回接收到的消息的长度,失败返回-1
e. 基于消息队列实现进程间的通信
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/msg.h>
struct msgbuf{
long mytype;
char mtext[128];
};
int main(void)
{
int msgid;//共享内存id
key_t key;//共享内存key值
pid_t pid; //进程id
struct msgbuf msg;
key=ftok("./memory2.c",'m');
msgid=msgget(key,0666|IPC_CREAT);
if (msgid<0){
printf("msgget is error\n");
return -1;
}
printf("shmid is %d\n",msgid);
msg.mytype=1;
strncpy(msg.mtext,"hello",5);
pid=fork();
if(pid>0){
msgsnd(msgid,&msg,strlen(msg.mtext),0);
sleep(3);
exit(0);
}
if(pid==0){
sleep(2);
msgrcv(msgid,(void *)&msg,128,0,0);
printf("msg is %s\n",msg.mtext);
exit(0);
}
return 0;
}
(5)信号量
不以传输数据为目的,而是保护共享资源,本质是一个计数器,也是存在于内核空间中,因此使用信号量也是需要创建的
a. semget创建
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
:int semget(key_t key, int nsems, int semflg);函数功能:获得信号量的ID*
参数:
key_t key: 信号量的键值。
int nsems: 信号量的数量。信号量集合中包含的信号量个数
int semflg: 标识
返回值:成功返回信号量的ID;失败返回-1
b. semctl 删除、属性设置
int semctl(int semid, int semnum, int cmd, union semun arg);
参数:
int semid: 信号量ID,
int semnum: 信号量编号
cmd: IPC_STAT(获取信号量的属性)IPC_SET(设置信号量的属性)IPC_RMID((删除信号量)SETVAL(设置信号量的值)
// 设置信号量属性、值 时需要用到的结构体
arg:union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
c. semop 信号量PN操作
int semop(int semid, struct sembuf *sops, size_t nsops);
参数:
int semid 信号量ID,
struct sembuf *sops 信号量结构体数组
size_t nsops 要操作信号量的数量
struct sembuf{
unsigned short sem_num; // 要操作的信号量的编号
short sem_op; //PN操作,1为V操作,释放资源。-1为P操作,分配资源。0为等待,直到信号量的值变成Oshort
sem_flg; //O表示阻塞,IPC_NOWAIT表示非阻塞
};
d. 基于信号量实现子进程先运行
#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <unistd.h>
union semun {
int val;
};
//注意不要定义struct sembuf,因为已经在#include <sys/sem.h>里定义了,会报重定义的错误
//struct sembuf{
// unsigned short sem_num; // 要操作的信号量的编号
// short sem_op; //PN操作,1为V操作,释放资源。-1为P操作,分配资源。0为等待,直到信号量的值变成Oshort
// short sem_flg; //O表示阻塞,IPC_NOWAIT表示非阻塞
//};
int main(void)
{
int semid;
int key;
pid_t pid;
union semun semun_union;
struct sembuf sem;
key=ftok("./a.c",0666);
semid =semget(key,1,0666|IPC_CREAT);
semun_union.val=0;
semctl(semid,0,SETVAL,semun_union);
pid=fork();
if(pid>0){
sem.sem_num=0;
sem.sem_op=-1;
sem.sem_flg=0;
semop(semid,&sem,1);
printf("this is parent\n");
sem.sem_num=0;
sem.sem_op=1;
sem.sem_flg=0;
semop(semid,&sem,1);
}
if(pid==0){
sleep(2);
printf("this is son\n");
sem.sem_num=0;
sem.sem_op=1;
sem.sem_flg=0;
semop(semid,&sem,1);
}
return 0;
}