BUAA-SE-SP学习记录
参考资料:《Linux编程基础》
Linux环境基础
常用Linux命令
文件系统管理命令
命令 | 参数 | 作用 |
---|---|---|
cd | ||
ls | ||
pwd | ||
mkdir | ||
touch | ||
系统及用户管理命令
命令 | 参数 | 作用 |
---|---|---|
用户/组权限管理
权限操作命令
su : 切换到root , root 账户具有最高权限。返回当前用户则使用exit 。
sudo : 在指令前加上sudo ,使得本条指令以最高权限运行。
chmod : 使用 chmod 命令更改文件权限。
chown : 使用chown 命令更改文件所有者。
chgrp : 使用chgrp 命令更改文件的所属组。
useradd , groupadd : 添加用户/用户组 格式为 useradd/groupadd [选项] 用户名
passwd : 给用户设置密码。格式为passwd [选项] 用户名
userdel , groupdel : 删除用户 格式为 userdel/groupdel [选项] 用户名
usermod , groupmod : 用以修改用户和用户组的相关属性
# chmod修改权限的两种方式
chmod +x file
chmod 777 file
管道
可以把多个命令串联起来,使一个命令的输出作为另一个命令的输入命令1 | 命令2 | 命令3 ....| 命令n
环境变量
环境变量是用来定义系统运行环境的一些参数
默认环境变量
每个用户不同的家目录~HOME
邮件存放位置~MAIL
系统默认的查找可执行文件的路径~PATH
自定义环境变量
env
查看所有环境变量export 变量名
将一个本地变量修改为环境变量export 变量名=值
定义一个环境变量
永久设置环境变量
把上面提到的export语句写到~/.bashrc
文件(mac是 ~/.bash_profile
中)
mac使用zsh的话就是 ~/.zshrc
PATH
PATH 是一个环境变量,这个环境变量指明了系统默认的查找可执行文件的路径。你可以在 bash shell中使用echo $PATH
打印出你当前的PATH
PATH实际上是几个路径之间用: 拼接起来的。
有了PATH ,当你在命令行输入一个程序名时,bash shell 就会去PATH 所指定的这几个目录中去寻找该程序,如果找不到就会报错。
使用which指令查看指令(即程序)的具体路径
Shell编程
Shell基础
shell是命令解析器,和shell交互的程序叫terminal(终端)
bash也是一种shell
bash/zsh/tcsh/fish/...
bash的配置文件是-/.bash_profile
zsh的配置文件是-/.zshrc
脚本的执行
操作系统在执行该脚本文件的时候,会用一种解释器(bash、Python 等)来逐行解释执行该文件中的指
令bash test.sh
python test.py
请始终注意,文件拓展名大多数情况下都是给人看的,操作系统不会自动把.py结尾的文件看做是 Python 文件指定脚本运行使用的解释器(shabang)
#!/bin/bash
放在脚本第一行,指定所用的解释器
执行这种脚本只需文件名即可:./test.sh
命令连接符(短路)
&&
只要有假就停止||
只要有真就停止变量
变量名=值
$var
((var+=5))位置变量(传入参数)
在执行 Shell 脚本的时候,可以传入参数,如当前有个脚本叫test ,执行sh test arg1
arg2 arg3 ,那么在test 中, $0 代表脚本文件名, $1 为第一个参数: arg1 ,以此类推。Shell指令
grep
Globel Search Regular Expression and Printing out the line
全面搜索正则表达式并把行打印出来
grep
基本正则表达式grep -E
/ egrep
扩展正则表达式
不加引号:过滤字符串
加引号:模式匹配grep -P
支持数字 \d
匹配
read
标准输入read var
read -r var
忽略转义符
date
输出当前时间
mv
重命名,或移动文件mv abc 123
把abc重命名为123
引号符号
把指令的执行结果作为参数,供另一个指令使用
单引号:所见即所得
- 不会解析里面的变量,全部当作字符串原样输出
双引号:所见非所得
- 先把变量解析之后,再输出
反引号(``) :命令替换,通常用于把命令输出结果传给入变量中
RESULT=
md5sum/home/wzx/Desktop
.zip``- for file in
ls
指令参数
$0 指令内容
$1 第一个参数
$2 第二个参数sed
字符串替换(重命名)
-r选项: --regexp-extended,支持扩展正则表达式,否则扩展正则表达式的相关符号都要加转义\
一般前面用echo+管道作为输入
使用格式:sed -r "s/ 修改前_用括号指定修改哪里 / 修改后 /g"
正则表达式第1个()括号里面代表第一段字符串即\1echo "aaa bbb" | sed -r 's/(.)/A/' Aaa bbb echo 202.038.008.090 | sed -r 's/0+([0-9]+)/\1/g' 22.38.8.90 # 使用sed文件重命名(末尾加owner) mv $file `echo $file | sed -r "s/(.*)/\1[$owner]/g"`
stat
获取文件信息
owner=`stat -c %U $file`
Shell控制语句
条件语句
if [];then xxx elif []; then xxx else xxx fi # 在if中使用正则判断 if [[ $str =~ ^[0-9]+$ ]];then # 在if中使用通配符判断 if [[ "$str" == "hello"* ]];then
while循环语句
while [] do xxx done
for循环语句
for var in 1 2 3 4 5 6 do xxx done
case语句
case $var in expr1) xxx ;; expr2) xxx ;; *) xxx ;; esac
select语句
select var in "A" "B" "C" do xxx done
注意菜单列表用双引号扩起来,用空格分割
Shell条件表达式
test 表达式
或 [ 表达式
逻辑操作
操作 | 含义 |
---|---|
!expr | 逻辑非 NOT |
expr1 -a expr2 | 逻辑与 AND |
expr1 -o expr2 | 逻辑或 OR |
数值比较
表达式 | 含义 |
---|---|
-eq | 是否等于 |
-nq | 是否不等于 |
-gt | 是否大于 |
-ge | 是否大于等于 |
-lt | 是否小于 |
-le | 是否小于等于 |
文件操作
操作 | 含义 |
---|---|
-d filename | 若file为目录,返回为真 |
-f filename | |
-s filename |
字符串
操作 | 含义 |
---|---|
$str1 = $str2 | 判断相等 |
$str1 != $str2 | 判断不等 |
-n $str | 判断非空 |
-z $str | 判断空 |
$str | 判断非空 |
Shell正则表达式
基础正则
符号 | 名称 | 含义 |
---|---|---|
^ | 行首定位 | ^po |
$ | 行尾定位 | conf$ |
. | 单字符 | 13. |
[] | 字符集 | [0-9a-zA-Z] |
* | 匹配前导字符任意次数 | s* |
扩展正则
+ | 匹配前导字符至少一次 | s+ | |||
---|---|---|---|---|---|
? | 匹配前导字符最多一次 | s? | |||
` | 和 ()` | 取或 | (aa | bb | cc) |
{} | 匹配前导字符几次 | {3} | |||
\d | 数字字符 | ^rc\d | |||
\s | 空白字符 | \s+ |
Shell通配符
*
所有文件p*
所有p开头的文件*.txt
所有txt文件data???
data开头,后跟三个字符的文件[0-9]*
以三个数字开头的文件
文件I/O操作与文件系统
常用的文件操作函数
int create(const char *filename, mode_t mode);
create 函数的功是创建文件,如果创建成功会返回一个文件描述符,创建失败则返回-1。
int open(const char *pathname, int flags(,mode_t mode));
open 函数的功能是打开创者创建一个文件。如果文件打开成功,open 函数会返回一个文件描述符,以后对该文件
的所有操作就可以通过对这个文件描述符进行操作来实现。open 函数有两个形式,其中 pathname 是要打开的文件
名。
int close(int fd);
关闭文件,成功调用则返回 0,否则返回-1。
int read(int fd, const void *buf,size_t length);
函数 read 实现从文件描述符 fd 所指定的文件中读取 length 个字节到 buf 所指向的缓冲区中,返回值为实际读取的
字节数。
int write(int fd, const void *buf,size_t length);
函数 write 实现将 length 个字节从 buf 指向的缓冲区中写到文件描述符 fd 所指向的文件中,返回值为实际写入的字
节数。
int lseek(int fd, offset_t offset, int whence);
lseek()将文件读写指针相对 whence 移动 offset 个字节。操作成功时,返回文件指针相对于文件头的位置。
int fcntl(int fd, int cmd, ...);
用来修改已经打开文件的属性的函数 。
int stat(const char *path,struct stat *buf);
用于获取文件的属性。参数 path 为文件路径。当函数调用成功之后,可以通过读取 buf 中的信息获取文件属性。
int access(const char *pathname,int mode);
用于测试文件是否拥有某种权限。参数 pathname 为文件名,参数 mode 可取四个值,分别表示测试文件是否具有
读、写、执行权限和文件是否存在。如果满足 mode 中值所代表的条件,则返回 0,否则返回-1。
int chmod(const char *path,mode_t mode);
用于修改文件的访问权限。
int truncate(const char *path,off_t length);
用于修改文件大小。
目录
Linux目录结构
inode 索引节点
inode包含文件的元信息
inode的内容
- 文件的字节数
- 文件拥有者的User ID
- 文件的Group ID
- 文件的读、写、执行权限
- 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。
- 链接数,即有多少文件名指向这个inode
- 文件数据block的位置
每一个文件数据都对应唯一的inode
可以有很多文件名指向同一个inode
用 stat filename
查看
注意不包含文件名!
inode的大小
操作系统自动在硬盘中保留inode区来存放
inode号码
每个inode都有唯一的号码,操作系统只通过inode号码区分不同文件
文件名-inode号码-inode信息-文件数据位置-读出数据
目录文件
目录的储存形式也是文件,是保存着一系列目录项的列表
每个目录项=文件名+inode号码ls -i
命令查看inode号码ls -li
带inode号码的文件详细信息
文件与目录的属性
硬链接
多个文件名指向同一个inode号码
可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。
ln 源文件 目标文件
每多一个指向inode的文件名,这个inode的链接数+1,每少一个就-1
软链接
是一个新文件,分配一个新的inode
A和B的inode不同,A的内容是B的路径,读取A时自动导向B
软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode"链接数"不会因此发生变化。
ln -s 源文件 目标文件
文件权限(ls -l)
drwxrwxr-x 2 leozhudd staff 4096 2007-10-26 17:20 Desktop
-rw-r--r-- 1 leozhudd staff 233 2007-10-26 13:10 test.sql
第一个字符
- d:目录
- -:文件
之后三个一组,共有三组,分别对应拥有者/拥有组/其他人的权限
- r:读取
- w:写入
- x:执行
第二个:文件硬连接数目
为了简便,可以用数值来描述权限信息, r 对应的数值是4 , w 对应的数值是2 , x 对应的数值是1 。每组
权限信息是对这三个数值的简单相加,比如上图中的权限信息可以描述成755 (注意,这是一个八进制数)。
配合 chmod
命令修改文件权限
文件描述符 fd
非负整数,对应一个进程中打开的文件
一个文件可以被同一个进程打开多次
文件描述符实际上是一个索引值,其作用是索引到该进程的文件描述符表中的对应表项。文件描述符表的表项里有一指针,指向在内核中的打开文件表的表项,该表项中存有打开文件的文件偏移量、文件相关目录项 dentry 等相关属性信息。
标准文件I/O
文件 I/O
文件 I/O 称之为不带缓存的 IO(unbuffered I/O)。不带缓存指的是每个 read,write 都调用内核中的一个系统调用。也就是一般所说的低级 I/O——操作系统提供的基本 IO 服务,与 os 绑定,特定于 linux 或 unix 平台。
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/file.h>
#include <string.h>
#define MAXLEN 64
int main()
{
// open测试
int fd = open("/Users/leozhudd/Desktop/课程Doc/System Programming/sp-labs/lab04/src/t3", O_RDWR);
if(fd < 0){
perror("lab04/src/t3");
return 0;
}
// 申请一块内存用来存放数据
char* buff = (char*)malloc(MAXLEN);
memset(buff,0,MAXLEN);
// read测试
read(fd, buff, MAXLEN);
printf("%s\n",buff);
// write测试
strcpy(buff, "May the force be with you, zhumuqing!");
lseek(fd, 0, SEEK_SET);
write(fd, buff, strlen(buff));
// 输出文件当前内容(修改之后的)
lseek(fd, 0, SEEK_SET);
read(fd, buff, MAXLEN);
printf("%s\n",buff);
// 文件关闭
close(fd);
return 0;
}
标准 I/O
标准 I/O 是 ANSI C 建立的一个标准 I/O 模型,是一个标准函数包和 stdio.h 头文件中的定义,具有一定的可移植性。标准 I/O 库处理很多细节。例如缓存分配,以优化长度执行 I/O 等。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAXLEN 64
int main()
{
// fopen测试
FILE* fp = fopen("/Users/leozhudd/Desktop/课程Doc/System Programming/sp-labs/lab04/src/t3","r+");
if(fp == NULL){
perror("lab04/src/t3");
return 0;
}
// 申请一块内存用来存放数据
char* buff = (char*)malloc(MAXLEN);
memset(buff,0,MAXLEN);
// fread测试
fread(buff, sizeof(char), MAXLEN, fp);
printf("%s\n",buff);
// fwrite测试
strcpy(buff, "May the force be with you, zhumuqing!");
fseek(fp, 0, SEEK_SET);
fwrite(buff, sizeof(char), strlen(buff), fp);
// 输出文件当前内容(修改之后的)
fseek(fp, 0, SEEK_SET);
fread(buff, sizeof(char), MAXLEN, fp);
printf("%s\n",buff);
// 文件关闭
fclose(fp);
return 0;
}
对比
文件 I/O 中用文件描述符表现一个打开的文件,可以访问不同类型的文件如普通文件、设备文件和管道文件等。而标准 I/O 中用 FILE(流)表示一个打开的文件,通常只用来访问普通文件。
标准I/O是ANSI C标准中的C语言库函数,在不同的操作系统中应该调用不同的内核API,UNIX环境下,标准I/O是对文件I/O的封装。
文件I/O | 标准I/O | |
---|---|---|
打开文件 | open | fopen, freopen |
关闭文件 | close | fclose |
读取 | read | getc, fgetc, getchar |
fgets, gets,
fread |
| 写入 | write | putc, fputc, putchar
fputs, puts,
fwrite |
有关标准I/O拥有的缓冲区:
open/read/write和fopen/fread/fwrite的区别
open/read/write和fopen/fread/fwrite的区别
open和fopen和freopen
open:打开文件,返回文件表示符
fopen:打开文件,返回文件指针
freopen:把标准输入/输出流stdin/stdout重定向到指定文件
int fd = open(char* path, int flags(, mode_t mode));
// path 文件所在路径
// flag = O_RDONLY/O_WRONLY/O_WDLR/O_CREAT
// 其中O_CREAT是如果文件不存在就新建文件,对应需要mode参数指定权限(0744)
FILE* fp = fopen(const char* path, const char* mode);
// mod = r/r+/w/w+/a/a+/...
FILE *freopen( const char *path, const char *mode, FILE *stream );
// 一般可以不使用它的返回值
read/write和fread/fwrite
ssize_t numread = read(int fd, void *buf, size_t length);
ssize_t result = write(int fd, void *buf ,size_t length);
// buf指向要读写的数据内存地址,length是要写入的字节数
fread(void* buf, size_t size, size_t count, FILE* fp);
fwrite(void* buf, size_t size, size_t count, FILE* fp);
// size是要读写的单个元素的字节数,count是进行多少个size字节的元素的读写
// 返回值:读取的总数据元素个数
close和fclose
int close(int fd);
int fclose(FILE* fp);
lseek和fseek
移动文件读写指针
// 移动到文件开头
lseek(fd, 0, SEEK_SET);
fseek(FILE* fp, 0, SEEK_SET)
fflush
定义函数:int fflush(FILE* stream);
函数说明:fflush()会强迫将缓冲区内的数据写回参数stream 指定的文件中. 如果参数stream 为NULL,fflush()会将所有打开的文件数据更新.
文件I/O的dup函数(复制文件描述符)
使多个文件描述符指向同一个文件
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。
C语言的Linux目录操作
基于 dirent.h
中的 opendir
和 readdir
具体使用方法看下面代码
// 要求输入一个参数代表指定路径,打印路径下所有文件的名称
// SP-Lab04-6
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
void list_files(DIR* dir){
struct dirent* pdir;// dirent结构保存每个文件的信息,即pdir是指向文件的指针
// 通过句柄dir,调用readdir来获取目录下的文件
while((pdir = readdir(dir)) != NULL){
// 不打印.和..这两个目录
if(strcmp(pdir->d_name,".") == 0 || strcmp(pdir->d_name,"..") == 0) continue;
printf("%s\n", pdir->d_name);
}
}
int main(int argc,char *argv[])
{
DIR* dir;
if(argc < 2){
printf("参数数量不正确!");
return 1;
}
// 打开目录,获取目录句柄
if((dir = opendir(argv[1])) == NULL){
perror(argv[1]);
return 1;
}
list_files(dir);
return 0;
}
文件锁
详情有待补充(教材)
请看代码注释
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
int fd;
struct flock lock,savelock;// 两个文件锁对象
fd = open("book.dat", O_RDWR);
lock.l_type = F_WRLCK;// 定义一个写入锁
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
savelock = lock;// 保存在savelock
fcntl(fd, F_GETLK, &lock);// 获得当前的文件锁信息,保存在lock
if(lock.l_type == F_WRLCK){
printf("Process %d has a write lock already!\n", lock.l_pid);
exit(1);
}
else if(lock.l_type == F_RDLCK){
printf("Process %d has a read lock already!\n", lock.l_pid);
exit(1);
}
else
fcntl(fd, F_SETLK, &savelock);// 未被锁,则设置写入锁
}
Linux进程管理
进程的概念
一个进程包括以下内容:程序代码(文本),当前活动(程序计数器,寄存
器的值),堆栈,数据端,堆
进程的创建
每个进程都有一个非负整型表示的唯一进程 ID—— pid
在命令后面加 &
符号:进程后台运行
创建子进程
创建一个子进程,共享父进程所有内容,并且这个子进程会接着 fork 下面的代码继续执
行。
#include <unistd.h>
pid_t result = fork();
fork()返回值:
- 出错返回-1
- 子进程返回0
- 父进程返回子进程ID
fork()的两种用法:
- 一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段
一个进程要执行一个不同的程序。在这种情况下,子进程从fork返回后立即调用exec
// 同时创建多个子进程的方法 #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; for(int i=1;i<=5;i++){ pid = fork(); //循环中,fork函数调用五次,子进程返回0,父进程返回子进程的pid, //为了避免子进程也fork,需要判断并break if(pid == 0) break; } if(pid > 0){ printf("父进程: pid= %d\n", getpid()); sleep(1); //这里延迟父进程程序,等子进程先执行完。 } else if(pid == 0){ printf("子进程: pid= %d\n", getpid()); } return 0; }
使用exec函数执行新的程序
与一般情况不同,exec 函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程 ID 等一些表面上的信息仍保持原样。只有调用失败了,它们才会返回一个 -1,从原程序的调用点接着往下执行。
当进程调用一种exec 函数时,该进程执行的程序完全替换为新程序,而新程序从其main函数开始执行。
调用exec 并不创建新进程,前后的进程 ID 并未改变,exec 只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ...
/* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]); // 第一个参数使用
的是打开的文件描述符,而非文件路径名
// 7个函数返回值:若出错,返回-1;若成功,不返回
// execl函数实例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
if(pid == 0) {
// 子进程转移执行另一个程序
execl("calc","calc","0","3","4");// 传给那个程序的命令行参数
}
waitpid(pid,NULL,0);
puts("程序结束");
return 0;
}
进程退出
守护进程
脱离于终端控制,并且在后台运行的进程
僵尸进程
当子进程先于父进程结束,同时父进程没有使用 wait()
获取子进程的结束状态时,子进程就成为僵尸进程
孤儿进程
在子进程终止之前,父进程先终止
使用exit处理进程终止
#include <stdlib.h>
void exit(int status);
void _Exit(int status);
#include <unistd.h>
void _exit(int status);
等待进程终止并获取退出状态
在父进程调用 wait()
等待子进程结束,并获取进程的返回状态(结束时传给exit的值)
wait()会暂时停止目前进程的执行, 即阻塞父进程,等待子进程结束或者其他信号。
#include <sys/wait.h>
pid_t wait(NULL);
pid_t wait(int *status)
pid_t waitpid(pid_t pid, int *statloc, int options); // 可以指定等待的子进程pid
返回值
- 成功:返回子进程pid
失败:返回-1
进程间通信
管道、信号量、共享内存
可以通过ipcs
指令查看以上内容
https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/ipcs.html#重定向
重定向产生的原因就是文件描述符在分配时趋向于数值小的,而在用户层,stdout 这个文件指针指向的文件已经封装了,并且它的 fd 就是 1,这是不能修改的,所以我们一上来关闭了 1 号文件,然后新创建了一个文件它的文件描述符就会分配为被 1,同时此时写入时,像 printf 这类函数默认使用的输出流就是 stdout,但是我们知道它的 1 指向的已经是我们新生成的那个文件了,所以这就重定向的本质。
// 两种方法 // 第一种:使用文件IO #include <stdio.h> #include <unistd.h> #include <sys/file.h> int main(int argc, char *argv[]) { close(1); open("log.txt",O_WRONLY); printf("233"); return 0; } // 第二种 使用标准IO #include <stdio.h> int main(int argc, char *argv[]) { fclose(stdout); fopen("log.txt","w"); printf("233"); return 0; }
管道
管道是最基本的进程通信机制,可以想象成一个管道,两端分别连着 2 个进程,一个进程往里面写,一
个进程从里面读。如果读或写管道的时候没有内容可供读或写,进程将被阻塞,直到有内容可供读写为
止。
匿名管道
匿名管道创建后本质上是 2 个文件描述符,父子进程分别持有就能够使用管道,需要注意的是不能够共用匿名管道,也就是除了使用的进程,其他进程需要关闭文件描述符,保证管道的 2 个描述符分别同时只有 1 个进程持有。
#include <unistd.h>
int pipe(int filedes[2]);
// 调用成功返回0,失败返回-1
调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。
// 父子进程间管道通信实例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <string.h>
char sendbuf[] = "A_song_for_You!_You?_You!!";
char recbuf[20];
char parrecbuff[20];
int main(int argc, char *argv[])
{
// 创建管道所用的文件描述符数组
int filedes[2];
pid_t pid;
if(pipe(filedes) < 0) {
perror("pipe failed");exit(1);
}
// 创建子进程
if((pid=fork()) < 0) {
perror("fork failed");exit(1);
}
if(pid == 0) {
read(filedes[0], recbuf, strlen(sendbuf)); //2. 从管道接收数据
write(filedes[1], recbuf+strlen(recbuf)-5, 5); //3. 把最后五位重新发送回管道
}
else {
printf("The begining str is: %s\n",sendbuf);
write(filedes[1], sendbuf, strlen(sendbuf)); //1. 向管道发送数据
sleep(5); //4. 等待子进程从管道将数据取走并再次返回
read(filedes[0], parrecbuff, strlen(sendbuf));
printf("The final str is: %s\n",parrecbuff);
wait(NULL);
}
}
有名管道
无名管道只能在父子进程间通信,而有名管道没有这个限制。
命名管道是一种特殊类型的文件(可以用 ls -l filename
查看)
命名管道是根据路径来使用管道, 故能够在任意进程间通信。
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
可以用open系统调用打开管道文件,注意打开时权限不要选O_RDWR,因为FIFO管道是单向的
// 通过有名管道发送文件内容
// A.c
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
int main()
{
int fd;
char s[150];
// 先把文件内容读入字符串保存
FILE* fp = fopen("./file", "r");
fgets(s, sizeof(s), fp);
fclose(fp);
// 创建有名管道并写入数据
mkfifo("./named_FIFO", 0777);
fd = open("./named_FIFO", O_WRONLY);
write(fd, s, strlen(s) + 1);
close(fd);
return 0;
}
// B.c
int main()
{
int fd;
char s[150];
// 打开管道并读入数据到字符串
fd = open("./named_FIFO",O_RDONLY);
read(fd, s, 1024);
close(fd);
// 把字符串写入文件
FILE* fp = fopen("./file2", "w+");
fputs(s, fp);
fclose(fp);
return 0;
}
消息队列
消息队列本质上在内核空间中开辟了一块内存空间,这块内存是其他进程可以访问到的,在其中使用链
表的方式实现了一个队列,进程可以向该队列中发送数据块或读取数据块,从而达到进程间通信的目
的。其中每个数据块包含两部分,首先是一个类型为 long 的 type,然后是具体的数据,其中的这个
type 就可以作为进程之间相互约定好的协议,即你发送 type 为15131049 的消息,我接收 type 为
15131049 的消息,我确认这就是你发出来的,我信任该数据块中的数据。
消息类型(type):大于0的整数,每条消息都有自己的消息类型。其中类型为0的消息维护所有消息加入队列的顺序(红色)
// 创建一个消息队列,key是消息队列编号
// flag指定权限:IPC_CREAT表示如不存在就创建新的消息队列,加上IPC_EXCL表示创建新的消息队列,无法创建时报错,通常要加上操作权限,比如0666
// 返回值是新建的消息队列id
int msgget(key_t key,int flag);
// 定义消息结构
typedef struct {
long type; // 消息类型(正整数)
char message[255]; // 消息正文
}Msg;
// 将消息发送到消息队列
// msqid=ipc内核对象id,msgp=消息数据地址,msgsz=消息正文大小
// msgflg =0自动阻塞/=IPC——NOWAIT不阻塞
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
//删除之前的消息队列
msgctl(id_up, IPC_RMID, 0);
信号量
信号量最好理解为“信号灯”,就相当于红绿灯 🚦一样,用来在进程遭遇“岔路口”的时候,通知进程做怎样的工作。其本质是为了实现多个进程之间的同步。
信号量是一种数据结构,说明某个资源可用的数量,用于多进程对共享数据对象的读取。
- 进程使用资源执行
P(sv)
操作,信号量-1 - 如果-1后大于等于0,则正常执行,如果小于0,则把当前进程放进等待队列
进程执行完后释放资源执行
V(sv)
,从等待队列中选一个进程执行,如果队列为空则sv+1二值信号量
如果资源只有一份,那么信号量的初始值将是1 ,最多只能有一个进程使用该资源。这种信号量被称
为“二值信号量”。这时信号量的作用就失去了指示“当前还有多少资源可用”的意义,仅仅用来标明“当前
资源是否可用”,就蜕化成了“互斥锁”的作用(当然,二值信号量与互斥锁有本质的不同),其作用就类
似于大家在 Java 中学到的synchronized (当然, synchronized 要实现的是线程之间的同步)。SystemV无名信号量
只能用于父子进程或同一进程多个线程之间。
// 创建信号量 int semget(key_t key,int nsems,int flags) /* (1)第一个参数key是长整型(唯一非零),系统建立IPC通讯 ( 消息队列、 信号量和 共享内存) 时必须指定一个ID值。 通常情况下,该id值通过ftok函数得到,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就可以。 (2)第二个参数nsem指定信号量集中需要的信号量数目,它的值几乎总是1。 (3)第三个参数flag是一组标志,当想要当信号量不存在时创建一个新的信号量,可以将flag设置为IPC_CREAT与文件权限做按位或操作。 返回:这个信号量的标识符id */ // 信号量操作控制buf struct sembuf sembuf; sembuf.sem_num = 0; //除非使用一组信号量,否则它为0 sembuf.sem_op = +1; // 信号量在一次操作中需要改变的数据,-1:P(等待)操作, +1:V(发送信号)操作 sembuf.sem_flg = SEM_UNDO; // 通常为SEM_UNDO,使操作系统跟踪信号量,在进程没有释放该信号量而终止时,操作系统释放信号量 // 改变信号量的值 int semop(int semid, struct sembuf *sops, size_t nops); // nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作 // 结合上面两点,封装P/V操作 int sem_p(int sem_id){ struct sembuf sembuf; sembuf.sem_num = 0; sembuf.sem_op = -1; sembuf.sem_flg = SEM_UNDO; if(semop(sem_id,&sem_buf,1)==-1){ perror("P opration failed"); return -1; } return 0; } int sem_v(int sem_id){ struct sembuf sembuf; sembuf.sem_num = 0; sembuf.sem_op = 1; sembuf.sem_flg = SEM_UNDO; if(semop(sem_id,&sem_buf,1)==-1){ perror("V opration failed"); return -1; } return 0; }
POSIX有名信号量
信号值保存在文件中,可实现任意两个进程的通信。
// 1. 创建 sem_t* sem_open(const char* name, int oflag, mode_t mode, int value); // name=文件路径,oflag=创建模式,mode_t=访问权限,value=信号量初始化值 // 例如: sem_t *mysem; mysem = sem_open("POSIXSEM", O_CREAT, 0666, 1); // 2. 操作 sem_wait(); // P操作 sem_post(); // V操作 // 例如: sem_wait(mysem); // 3. 结束 sem_close(mysem); sem_unlink("POSIXSEM");
共享内存
// 子进程写入共享内存,父进程读出来 #include<sys/shm.h> #include<sys/ipc.h> #include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<string.h> int main() { int shmid; // 共享内存段标识符 char *shmaddr; // 共享内存映射地址(可读写) char buff[BUFSIZ]; int shmstatus; // 获取共享内存属性信息 shmid = shmget(IPC_PRIVATE, BUFSIZ, IPC_CREAT | 0600); // 创建共享内存 shmaddr = (char *)shmat(shmid, NULL, 0); // 映射共享内存地址 pid_t pid; if((pid=fork()) == 0){ // 子进程 strcpy(shmaddr, "HELLO SHM!"); shmdt(shmaddr); // 释放所指向的地址 return 0; } else if(pid > 0){ // 父进程 sleep(5); strcpy(buff, shmaddr); printf("Got from shared memory: %s\n",buff); shmdt(shmaddr); // 释放所指向的地址 shmctl(shmid, IPC_RMID, NULL); // 退出时删除共享内存实例 } return 0; }
信号及信号处理
信号基本概念
Linux进程间异步通信的机制
信号传递一种信息,接收方根据信息进行相应动作include <signal.h>
信号的产生
- Ctrl+C/Ctrl+/Ctrl+Z
- 非法内存
- 硬件异常
- 环境切换
系统调用kill/raise/sigsend
信号的状态
- 递送(delivery):进程对信号采取动作
- 未决(Pending):信号从产生到递达之间的状态
阻塞(block):进程可以选择阻塞某个信号,此时信号就会处于Pending状态,直到阻塞解除或忽略处理
信号的分类
可靠/不可靠信号
(早期机制)信号值小于SIGRTMIN为不可靠信号,同时发生多个信号时,只保留一个,剩下被丢掉
信号值在SIGRTMIN和SIGRTMAX之间为可靠信号,同时发生多个信号时,排入队列依次处理实时/非实时信号
使用
kill -l
命令查看
前32种为非实时信号(不可靠,可能丢失)
后32种为实时信号(可靠,支持排队)信号的发送和处理
- 默认处理
signal(SIGINT,SIG_DEF)
- 忽略信号
signal(SIGINT,SIG_IGN)
捕捉并处理
signal(SIGINT,func)
信号的捕捉
signal函数
signal(int signum, void (*action)(int)) // signum为要捕捉的信号 // action为自定义信号处理函数,也可以为SIG_DEF/SIG_IGN,即DEFAULT/IGNORE
sigaction函数
int sigaction(int signum, const struct sigaction* act, const struct sigaction* oldact); // signum:要捕捉的信号 // act:sigaction类型的结构体,包含自定义处理函数和其他信息 // oldact:传出参数,包含旧处理函数等信息(一般为NULL) // 系统写好的结构体,可以直接创建 stuct sigaction { void (*)(int) sa_handle; sigset_t sa_mask; int sa_flags; } // 实例1(信号有附加信息) struct sigaction act; act.sa_flags = SA_SIGINFO; act.sa_sigaction = sigHandler; sigaction(SIGUSR1, &act, NULL); // 实例2(简单信号) struct sigaction act; act.sa_handle = sigHandler; sigaction(SIGUSR1, &act, NULL);
捕捉后的信号处理函数
// 如果信号没有附加数据 void handler(int sig){}; // 如果要接收信号附加数据 void handler(int sig, siginfo_t *info, void *ucontext){}; //第一个参数sig代表接收信号的值 //第二个参数info是指向siginfo_t类型的指针,包含了有关信号的附加信息 //第三个参数ucontext是内核保存在用户空间的信号上下文,一般不使用该参数 此时如果接收进程使用sigaction()注册信号处理函数,并将sa_flags字段置为SA_SIGINFO,那么在 信号处理函数中可以通过info参数的si_value 获取到发送信号伴随的数据, 如info->si_value.sival_int 或info->si_value.sival_ptr 。
信号的发送
kill函数
int kill(pid_t pid, int sig); // pid代表接收信号的进程PID,sig表示要发送什么信号
sigqueue函数
int sigqueue(pid_t pid, int sig, const union sigval value); // pid代表接收信号的进程PID,sig代表要发送的信号,value是联合体代表要传递的数据 // sigval的原型,用于给信号附加数据 union sigval { int sival_int; void *sival_ptr; }; // 实例 union sigval mysigval; mysigval.sival_int = 114514; sigqueue(pid, SIGUSR1, mysigval);
信号的屏蔽
在sigaction中屏蔽
sa_mask:信号屏蔽集,可以通过函数sigemptyset/sigaddset等来清空和增加需要屏蔽的信号。
// 对信号SIGINT处理时,如果来信号SIGQUIT,其将被屏蔽
// 但如果在处理SIGQUIT,来了SIGINT,则首先处理SIGINT,然后接着处理SIGQUIT
struct sigaction act;
act.sa_handler = sigHandler;
// 下面设置要屏蔽的信号
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGQUIT);
全局信号集屏蔽:sigprocmask
信号集:多个信号编成一个集合,对集合内的信号进行同样的操作
信号集在使用前一定要用sigemptyset或sigfillset初始化
// 信号集设定函数
int sigemptyset(sigset_t *set); //将set指向的信号集初始化为不包含任何信号
int sigfillset(sigset_t *set); //将set指向的信号集初始化为包含所有信号
int sigaddset(sigset_t *set, int signo);//向信号集添加信号signo
int sigdelset(sigset_t *set, int signo);//向信号集删除信号signo
int sigismember(const sigset_t *set, int signo);//判断信号signo是否在信号集中
sigprocmask:用来修改进程的信号屏蔽字,它可以屏蔽某个信号会对某个已经屏蔽的信号解除屏蔽
// 信号集函数
int sigprocmask(int how,const sigset_t* set,sigset_t* oldset);
//第一个参数用于设置位操作方式,第二个参数一般为用户指定信号集,第三个参数用于保存原信号集
//how=SIG_BLOCK:mask=mask|set
//how=SIG_UNBLOCK:mask=mask&~set
//how=SIG_SETMASK:mask=set
// 实例
sigset_t sigset;
sigfillset(&sigset); // 设置信号集为全部信号
sigprocmask(SIG_SETMASK, &sigset, NULL); // 设置信号集屏蔽
定时信号
alarm函数
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
//第一个参数seconds用来指明时间,经过seconds秒后发送SIGALRM信号给当前进程,当参数为0则取消之
前的闹钟
返回值:
如果本次调用前已有正在运行的闹钟,alarm()函数返回前一个闹钟的剩余秒数
如果本次调用前无正在运行的闹钟,alarm()函数返回0
多线程编程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
pthread_mutex_t t; // 线程锁
void* T1_exec(void* arg){
pthread_mutex_lock(&t); // 上锁
pthread_mutex_unlock(&t); // 解锁
return NULL;
}
int main()
{
pthread_mutex_init(&t,NULL); // 线程锁初始化
pthread_t T1; // 线程id
int ret = pthread_create(&T1, NULL, T1_exec, NULL); // 线程创建并执行
pthread_join(T1,NULL); // 阻塞,等待子线程结束
pthread_mutex_destroy(&t); // 销毁线程锁
return 0;
}
pthread_cond_t cond; // 条件变量
pthread_cond_signal(&cond); // 改变条件(相当于设置flag),并发送广播给其他线程
pthread_cond_wait(&cond, &t); // 等待并释放锁
错误处理
还没看。
11 comments
哈哈哈,写的太好了https://www.lawjida.com/
《Running man 2010》日韩综艺高清在线免费观看:https://www.jgz518.com/xingkong/148200.html
《爱乐之都》大陆综艺高清在线免费观看:https://www.jgz518.com/xingkong/49313.html
《极盗者极盗者》动作片高清在线免费观看:https://www.jgz518.com/xingkong/45396.html
《诈欺担保人第三季》欧美剧高清在线免费观看:https://www.jgz518.com/xingkong/38137.html
《密室大逃脱第五季大神版》大陆综艺高清在线免费观看:https://www.jgz518.com/xingkong/45157.html
想想你的文章写的特别好https://www.237fa.com/
叼茂SEO.bfbikes.com
叼茂SEO.bfbikes.com
表评论4123
表评论8718