野火【第二期】Linux系列教学视频之“内核编程”篇,手把手教学,硬件基于野火i.MX6ULL Pro/MINI开发板_哔哩哔哩_bilibili
看第一遍只简单总结了一些文字,第二遍实操、增加大量截图
P1 第48讲 进程的由来
程序是静态文件,进程是运行中的实体
查看进程间的关系:pstree
区分进程:PID;ps -ef | more【每次只显示一屏,按q退出】
P2 第49讲 创建一个进程
fork函数:头文件、函数原型、返回值
函数特性:复制1个进程;返回两次--新进程返回0,老进程返回新进程PID;fork之前代码执行1次,之后代码执行2次
c函数演示
P3 第50讲 子进程偷梁换柱
执行fork函数后子进程仍然与父进程程序相同,使用exec函数可以让子进程运行不同的程序
exec函数族:l--以列表list形式传参;lp--使用环境变量Path来寻找指定文件;v--以矢量数组vector形式传参;ve--用户提供自定义的环境变量
execl用法,将父进程替换为 ls 并传递参数 -lh 【指定ls程序路径】
execlp用法,将父进程替换为 ls 并传递参数 -lh【在PATH路径中寻找ls程序】
execv用法,将父进程替换为 ls 并传递参数 -lh
execve用法,貌似只是打印了env的字符
要点:l v 2选1;p e为可选功能;排列组合任选;可能执行失败,需要注意--路径错误、参数错误、权限不足
P4 第51讲 进程的退出
正常退出:main中return、exit、_exit
exit和_exit:_exit直接退出,不考虑IO缓存区;exit先处理IO缓存区
P5 第52讲 等待子进程的终结
wait函数:通过wait函数获取子进程退出状态;成功则返回子进程ID;子进程退出前会一直阻塞父进程【实际上不会直接用wait阻塞,而是子进程退出前发信号,父进程收到信号后调用wait】;子进程退出状态相关宏
若忘记函数对应的头文件,可以使用man命令查找,如下图
P6 第53讲 进程的生老病死
就绪态、运行态、睡眠态【可中断即响应中断,不可中断即不响应中断】、暂停态【包含暂停态和调试态,收到信号进入暂停态,收到调试命令进入调试态】、僵尸态、死亡态
转换关系、对应宏
P7 第54讲 进程组、会话、终端
进程组:管理相同类型的进程;
诞生--shell中执行应用程序、父进程fork生成子进程、shell中使用管道连接的应用程序;
进程组ID PGID 即首进程ID;
会话:管理进程组;
诞生--调用setsid函数、shell登录;
会话ID SID即会话首进程ID;
前台进程组:占用终端;后台转前台fg ID
后台进程组:不占用终端;指定为后台 & ctrl+z;jobs查看后台进程组
终端:开发板物理终端--串口、LCD;
伪终端--ssh远程连接、桌面系统启动的终端;
虚拟终端--linux内核自带,ctrl+alt+f0~f6
P8 第55讲 守护进程
会话管理前后台进程组,会话一般关联1个终端,终端关闭,会话进程全关闭。
守护进程:不受终端影响,始终后台运行;创建守护进程--fork、setsid、chdir、umask、close;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#define MAXFILE 3
int main()
{
pid_t pid;
int fd,len,i,num,cnt=0;
char *buf="the daemon is running\n";
char cnt_str[20];
int cnt_str_len;
len = strlen(buf)+1;
//1 创建子进程,销毁父进程
pid = fork();
if(pid<0)
{
printf("fork fail\n");
exit(1);
}
if(pid>0) //退出父进程
exit(0);
printf("son ok!\n");
fd = open("/var/log/daemon.log",O_CREAT|O_WRONLY|O_APPEND,0666);
printf("file ID is %d\n",fd);
if(fd<0)
exit(66);
//2 创建新会话,摆脱终端影响
setsid(); //子进程
//3 改变当前工作目录
chdir("/");
//4 重设文件权限掩码
umask(0); //相当于umask无效
//5 关闭默认的文件描述符
for(i=0;i<MAXFILE;i++)
close(i);
//6 实现守护进程的功能
while(1)
{
cnt++;
cnt_str_len = snprintf(cnt_str,20,"%d\t",cnt); //相当于行号
fd = open("/var/log/daemon.log",O_CREAT|O_WRONLY|O_APPEND,0666);
write(fd,cnt_str,cnt_str_len);
write(fd,buf,len);
close(fd);
sleep(1);
}
}
普通进程伪装成守护进程:nohup
P9 第56讲 ps命令详解
历史悠久、派系众多导致用法复杂
aux、axjf 字母含义:a--显示1个终端所有进程;u--显示进程的归属用户及内存使用情况;x--显示没有关联控制终端的进程;j--显示进程归属的进程组ID、会话ID、父进程ID;f--以ASCII形式显示出进程的层次关系
ps aux:各项内容;VSZ--虚拟内存大小;RSS--物理内存大小;TTY--关联终端;STAT--进程状态 熟悉DRSTXZ;command进程执行的具体程序;
ps axjf:各项内容;pgid进程组ID;tpgid守护进程;UID启动进程的用户;command进程的层次关系
使用场景:关注进程本身--ps aux;关注进程间关系--ps axjf
P10 第57讲 僵尸进程和托孤进程
进程的正常退出步骤:子进程exit、父进程wait;
僵尸进程:只有exit,没有wait
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int pid;
if( (pid=fork()) < 0)
{
printf("fail to fork\n");
return -1;
}
else
if(pid==0)
{
printf("child exit now\n");
exit(0); //子进程打印后退出
}
else
{
while(1) //父进程没有调用wait,子进程变为僵尸进程
sleep(1); //加点延时,避免ubuntu不响应
}
return 0;
}
托孤进程:父进程先结束,linux会把子进程托孤给1号进程init
P11 第58讲 什么是进程间通信(ipc)
数据传输、资源共享、事件通知、进程控制
linux系统4种ipc:项目开发常用posix
早期unix系统ipc--管道 数据传输、信号 事件通知、fifo 数据传输;
system-v ipc(贝尔实验室)--消息队列 、信号量 资源共享 进程控制、共享内存 数据传输【高效】;
socket ipc(BSD)--复杂,允许不同机器间通信;
posix ipc(IEEE)--消息队列、信号量、共享内存
P12 第59讲 无名管道
pipe函数【父子进程之间传输数据】
特点:特殊文件,无法open,可以close;子进程继承文件描述符;读写可能会阻塞进程【读空时读 或 写满时写】;相关文件关闭后管道销毁。
使用步骤:pipe、fork、close无用端口【一般1个进程不会同时使用读写端口】、write read、close读写端口
C文件演示:父进程写、子进程读
//父进程写、子进程读
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_DATA_LEN 256
int main()
{
pid_t pid;
int pipe_fd[2]; //0--读描述符 1--写描述符
int status;
char buf[MAX_DATA_LEN];
const char data[]="Pipe test program";
int real_read_num,real_write_num;
memset( (void *)buf,0,sizeof(buf) );
//创建管道
if( pipe(pipe_fd)<0 ) //创建管道失败
{
printf("pipe create error\n");
exit(1);
}
//创建子进程
if( (pid=fork()) == 0 ) //创建管道成功则创建子进程
{
//子进程关闭写描述符
close(pipe_fd[1]);
//子进程读取管道内容【无数据则阻塞】
if( ( real_read_num = read(pipe_fd[0],buf,MAX_DATA_LEN) ) > 0 )
printf("%d bytes read form the pipe is '%s'\n",real_read_num,buf);
//关闭子进程读描述符
close(pipe_fd[0]);
exit(0);
}
else if(pid>0)
{
//父进程关闭读描述符
close(pipe_fd[0]);
//父进程写数据【缓冲区满则阻塞】
if( ( real_write_num = write(pipe_fd[1],data,strlen(data)) ) != -1 )
printf("Parent write %d bytes : '%s'\n",real_write_num,data);
//关闭父进程读描述符
close(pipe_fd[1]);
//收集子进程退出信息
wait(&status);
exit(0);
}
}
P13 第60讲 有名管道
mkfifo函数【任意进程之间传输数据】
特点:有文件名,可以open;任意进程间传输数据;读写可能会阻塞进程【读空时读 或 写满时写】;write有原子性,要么全写,要么不写【缓冲区剩余空间足够则写,否则不写】
使用步骤:mkfifo、进程1 open write read、close、进程2 open write read、close
C文件演示:进程1写、进程2读
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//有名管道文件名
#define MYFIFO "/tmp/myfifo"
//4096 定义在 limits.h 中
#define MAX_BUFFER_SIZE PIPE_BUF
//参数为即将写入的字符串
int main(int argc,char *argv[])
{
char buff[MAX_BUFFER_SIZE];
int fd;
int nread;
//判断有名管道是否已存在,若尚未创建,则以相应的权限【666可读可写】创建
if( access(MYFIFO,F_OK) == -1 )
{
printf("Fifo doesn't exist\n"); //fifo不存在
if( (mkfifo(MYFIFO,0666)<0) && (errno!=EEXIST) )
{
printf("Can't create fifo file\n"); //创建fifo失败
exit(1);
}
printf("Create fifo success\n"); //创建fifo成功
}
else
printf("Fifo exist\n"); //fifo存在
//以只读阻塞方式打开有名管道
fd = open(MYFIFO,O_RDONLY);
if(fd==-1)
{
printf("Open fifo file error"); //打开fifo失败
exit(1);
}
printf("Open fifo file success\n"); //打开fifo成功
//循环读取有名管道数据
while(1)
{
memset(buff,0,sizeof(buff));
if( (nread = read(fd,buff,MAX_BUFFER_SIZE)) > 0 )
printf("Read '%s' from fifo\n",buff);
}
}
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//有名管道文件名
#define MYFIFO "/tmp/myfifo"
//4096 定义在 limits.h 中
#define MAX_BUFFER_SIZE PIPE_BUF
//参数为即将写入的字符串
int main(int argc,char *argv[])
{
char buff[MAX_BUFFER_SIZE];
int fd;
int nwrite;
if(argc<=1)
{
printf("Usage: ./fifo_write string\nPara error\n");
exit(1);
}
//填充命令行第一个参数到buff
sscanf(argv[1],"%s",buff);
//以只写阻塞方式打开有名管道
printf("Ready to open fifo\n");
fd = open(MYFIFO,O_WRONLY); //若管道未创建,则阻塞在这里
if(fd==-1)
{
printf("Open fifo file error\n"); //打开fifo失败
exit(1);
}
printf("Open fifo file success\n");
//向管道中写入字符串
if( (nwrite=write(fd,buff,MAX_BUFFER_SIZE)) > 0 )
printf("Write '%s' to fifo\n",buff);
close(fd);
exit(0);
}
P14 第61讲 信号简介
软件模拟中断,进程接收信号后作出相应响应
产生信号:硬件--执行非法指令、访问非法内存、驱动程序通知;软件--控制台命令、kill命令、程序调用kill函数
信号处理方式:忽略、捕获并调用函数、系统默认
P15 第62讲 常用信号分析
常用的是UNIX早期信号【kill -l的前31个信号】
SIGHUP--关闭终端;SIGINT--ctrl+c;SIGQUIT SIGABRT--ctrl+\ abort() 终止+转储【记录程序结束前的信息】;SIGPE--算术错误;SIGKILL--kill -9 不可捕获 不可忽略;SIGUSR1 SIGUSR2--自定义;SIGSEGV--段错误、非法内存;SIGALRM--alarm()【类似定时关机】;SIGTERM--kill pid;SIGCHLD--子进程状态变化;SIGSTOP【截图少了S】--ctrl+z 不可捕获 不可忽略
pkill命令:不看进程PID,使用进程名称
P16 第63讲 signal_kill_raise函数
signal函数:设置信号处理方式,设置信号对应的处理函数;
//第1次ctrl+c打印字符,第2次ctrl+c终止运行
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
//信号处理函数
void signal_handler(int sig)
{
printf("\n\nThe signal number is %d\n",sig);
if(sig==SIGINT)
{
printf("I have get SIGINT\n");
printf("The signal has been restored to the default processing mode\n\n");
//恢复信号为默认情况
signal(SIGINT,SIG_DFL);
}
}
int main(int argc,char *argv[])
{
printf("\nThis is an signal test function\n\n");
//设置信号处理的回调函数
signal(SIGINT,signal_handler);
while(1)
{
printf("Waitinig for the SIGINT signal , please enter \"ctrl+c\" ...\n");
sleep(1);
}
exit(0);
}
kill函数:给其他进程发送信号
raise函数:给本进程发送信号【相比kill,少了参数pid】
#include <signal.h>
int main(void)
{
pid_t pid;
int ret;
//创建子进程
if( (pid=fork()) < 0 ) //进程创建失败
{
printf("Fork error\n");
exit(1);
}
if(pid==0) //子进程
{
//在子进程中使用raise函数发出SIGSTOP信号,使子进程暂停
printf("Child is waiting for SIGSTOP signal\n\n");
//子进程停在这里【自己给自己发了停止信号】
raise(SIGSTOP);
//子进程不会执行到这里
printf("Child won't run here forever\n");
exit(0);
}
else //父进程
{
//睡眠3秒,让子进程先执行
sleep(3);
//发送SIGKILL信号杀死子进程
if( (ret=kill(pid,SIGKILL)) == 0 )
printf("Parent kill child which ID = %d\n\n",pid);
//一直阻塞直到子进程被杀死
wait(NULL);
//父进程退出运行
printf("Parent exit\n");
exit(0);
}
}
P17 第64讲 信号集处理函数
屏蔽信号集:手动;自动--处理某信号时再有信号自动屏蔽
未处理信号集:被屏蔽的信号发生时进入未处理信号集;非实时信号1-31,只保留1个;实时信号34-64,FIFO;
//运行后打印字符,按一次ctrl+c打印一次
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void my_func(int signo)
{
printf("\nhello\n");
sleep(5);
printf(" world\n");
}
int main()
{
signal(SIGINT,my_func);
while(1);
return 0;
}
信号集API:信号集全部清0、全部置1、某1位置1、某1位清0,sigprocmask信号集生效
//运行后打印字符,按一次ctrl+c打印一次
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void my_func(int signo)
{
//解除自动屏蔽SIGINT信号
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_UNBLOCK,&set,NULL);
printf("\nhello\n");
sleep(5);
printf(" world\n");
}
int main()
{
signal(SIGINT,my_func);
//signal(35,my_func);
while(1);
return 0;
}
上述代码极易出现ubuntu死机【使用gnome-system-monitor检测时也死机】,重启后就好,不知道为什么
P18 第65讲 system-V 消息队列
system-V ipc特点:unix系统的第5【V】个版本;独立于进程;key ID类似文件名和文件描述符
消息队列用法:ftok函数--获取key;msgget函数--获取或创建消息队列;msgsend函数--发送消息到消息队列;msgrcv函数--读取消息队列消息;msgctl函数--设置获取属性或删除消息队列
//利用终端发送消息,quit退出
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 512
struct message
{
long msg_type;
char msg_text[BUFFER_SIZE];
};
int main()
{
int qid;
key_t key;
struct message msg;
//根据不同的路经和关键字产生标准的key
if( (key=ftok("/tmp",11)) == -1 )
{
printf("ftok failed\n"); //生成key失败
exit(1);
}
//创建消息队列并得到ID【可读可写】
if( (qid=msgget(key,IPC_CREAT|0666)) == -1 )
{
printf("msgget failed\n"); //创建消息队列失败
exit(1);
}
printf("Open queue %d success\n",qid);
while(1)
{ //从控制台终端读取数据填充至结构体
printf("Enter some message to the queue:");
if( (fgets(msg.msg_text,BUFFER_SIZE,stdin)) == NULL )
{
puts("no message");
exit(1);
}
msg.msg_type = getpid();
//添加消息到消息队列【消息发送失败则阻塞在这里】
if( (msgsnd(qid,&msg,strlen(msg.msg_text),0)) < 0)
{
printf("message posted");
exit(1);
}
//在终端输入 quit 结束进程
if( strncmp(msg.msg_text,"quit",4) == 0 )
break;
}
}
//读取消息并打印在终端,quit退出
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 512
struct message
{
long msg_type;
char msg_text[BUFFER_SIZE];
};
int main()
{
int qid;
key_t key;
struct message msg;
//根据不同的路经和关键字产生标准的key
if( (key=ftok("/tmp",11)) == -1 ) //参数必须与发送程序一致
{
printf("ftok failed\n"); //生成key失败
exit(1);
}
//创建消息队列并得到ID【可读可写】
if( (qid=msgget(key,IPC_CREAT|0666)) == -1 )
{
printf("msgget failed\n"); //创建消息队列失败
exit(1);
}
printf("Open queue %d success\n",qid);
do
{
//读取消息队列【0--什么数据都要 0--阻塞读取】
memset(msg.msg_text,0,BUFFER_SIZE);
if( msgrcv(qid,(void*)&msg,BUFFER_SIZE,0,0) < 0 )
{
printf("message received");
exit(1);
}
printf("The message from process %ld : %s",msg.msg_type,msg.msg_text);
}while( strncmp(msg.msg_text,"quit",4) );
//从系统内核中移走消息队列
if( (msgctl(qid,IPC_RMID,NULL)) < 0 )
{
printf("msgctl");
exit(1);
}
exit(0);
}
P19 第66讲 system-V 信号量
本质:计数器
作用:保护共享资源--互斥、同步【在互斥的基础上增加顺序】
用法:fork函数;semget函数--获取 构造信号量;semctl函数--初始化信号量 获取或设置信号量属性;semop函数--信号量加减操作;删除信号量
利用信号量实现进程同步
//封装信号量底层函数
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
union semun
{
int val;
struct semid_ds *buf;
};
//初始化信号量,给信号量sem_id赋值init_value
int init_sem(int sem_id,int init_value)
{
union semun sem_union;
sem_union.val = init_value;
//给编号为0的信号量赋值
if( semctl(sem_id,0,SETVAL,sem_union) == -1 )
{
printf("Initialize semaphore\n");
return -1;
}
return 0;
}
//删除信号量
int del_sem(int sem_id)
{
union semun sem_union;
if( semctl(sem_id,0,IPC_RMID,sem_union) == -1 )
{
perror("Delete semaphore");
return -1;
}
}
//P操作,即减操作
int sem_p(int sem_id)
{
struct sembuf sops;
sops.sem_num =0; //单个信号量的编号为0
sops.sem_op = -1; //表示P操作
sops.sem_flg = SEM_UNDO; //系统自动释放系统中残留的信号量
if( semop(sem_id,&sops,1) == -1 )
{
perror("P operation");
return -1;
}
return 0;
}
//V操作,即加操作
int sem_v(int sem_id)
{
struct sembuf sops;
sops.sem_num =0; //单个信号量的编号为0
sops.sem_op = 1; //表示V操作
sops.sem_flg = SEM_UNDO; //系统自动释放系统中残留的信号量
if( semop(sem_id,&sops,1) == -1 )
{
perror("V operation");
return -1;
}
return 0;
}
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sem.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include "sem.h"
#define DELAY_TIME 3
int main(void)
{
pid_t result;
int sem_id;
//创建1个信号量【没有使用ftok函数,自定义6666,权限可读可写】
sem_id = semget( (key_t)6666,1,0666|IPC_CREAT );
init_sem(sem_id,0);
result = fork();
if(result==-1)
perror("Fork\n");
else if(result==0) //子进程
{
printf("Child process will wait for some seconds...\n");
sleep(DELAY_TIME);
printf("The child process is running...\n");
sem_v(sem_id);
}
else //父进程
{
sem_p(sem_id);
printf("The father process is running ...\n");
sem_v(sem_id);
del_sem(sem_id);
}
exit(0);
}
int init_sem(int sem_id,int init_value);
int del_sem(int sem_id);
int sem_p(int sem_id);
int sem_v(int sem_id);
P20 第67讲 system-V 共享内存
作用:高效率大量数据传输,不同进程虚拟地址映射到同一片物理内存
用法:ftok函数;shmget函数--获取 创建共享内存ID;shmat函数--映射共享内存;shmdt函数--解除映射;shmctl函数--获取 设置属性 删除共享内存
利用信号量实现进程同步,通过共享内存传输数据,相比上个视频,仅修改test.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include "sem.h"
#define DELAY_TIME 3
int main(void)
{
pid_t result;
int sem_id;
int shm_id;
char *addr;
//创建1个信号量【没有使用ftok函数,自定义6666,权限可读可写】
sem_id = semget( (key_t)6666,1,0666|IPC_CREAT );
//创建1个共享内存对象【没有使用ftok函数,自定义7777,权限可读可写】
shm_id = shmget( (key_t)7777,1024,0666|IPC_CREAT );
init_sem(sem_id,0);
result = fork();
if(result==-1)
perror("Fork\n");
else if(result==0) //子进程
{
printf("Child process will wait for some seconds...\n");
sleep(DELAY_TIME);
//映射共享内存【NULL--映射地址自动设置,0--可读可写】
addr = shmat(shm_id,NULL,0);
if(addr==(void *)-1)
{
printf("Shmat111 error\n");
exit(-1);
}
//设置共享内存中的内容
memcpy(addr,"helloworld",11);
printf("The child process is running...\n");
sem_v(sem_id);
}
else //父进程
{
sem_p(sem_id);
printf("The father process is running ...\n");
//映射共享内存地址
addr = shmat(shm_id,NULL,0);
if(addr==(void *)-1)
{
printf("Shmat222 error\n");
exit(-1);
}
printf("Shared memory string:%s\n",addr);
//解除共享内存映射
shmdt(addr);
shmctl(shm_id,IPC_RMID,NULL);
sem_v(sem_id);
del_sem(sem_id);
}
exit(0);
}