文章目录
World’s simplest shell
code
#include <sys/types.h>
#include <sys/wait.h> //定义了waitpid()函数需要用的符号常量
#include <errno.h> //定义了一系列错误的符号常量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
char *
getinput(char *buffer, size_t buflen){
printf("ss");
return fgets(buffer,buflen,stdin) //来自于unistd.h从stream中读取数据每次只读取一行,把读取的字符串存入buffer中,一行的鉴定是读取(n-1)个字符串,因为第n个字符串是\n,或者遇到EOF就返回NULL,错误也返回NULL,注意fgets会读取\n进buf,所以我们要处理把他变成\0 ,fget返回字符串,也就是字符数组,也就是char *
}
int main(int argc,char *argv[]){ //*argv[]和**argv一模一样都是数组指针
char buf[BUFSIZ]; //BUFSIZ定义域stdio.h里面的一个宏,大小8192
pid_t pid;
int status;
(void)argc; //避免编译器warning没有使用变量
(void)argv; //避免编译器warning没有使用变量
while(getinput(buf,sizeof(buf))){
buf[strlen(buf)-1]='\0'; //strlen不包含\0,此步骤为了处理fgets读取流的时候把\n度进去的情况
if((pid=fork())==-1){//如果fork出一个进程,该进程的pid等于-1那么就是fork失败
fprintf(stderr,"shell can't fork:%s\n",strerror(errno));//fprintf用于格式化输出到流中printf只能输出到stdout中,strerror()是string.h这个头文件中的函数,用于输入对应的错误代码,输出错误代码对应的报错提醒,errno定义于errno.h中对应的是系统最后一次错误代码就像echo $?一样
continue;
}else if(pid == 0){ //pid等于0说明是子进程也就是fork出的进程
execlp(buf,buf,(char *)0); //execlp是执行可执行文件但是不用带路径,系统会自定去PATH下找,比如我们输入ls就会自动去PATH下找,为什么2个相同的buf参数?第一个是可执行文件名字,第二个是argv[0],中间的参数其实可以一直加的,但是最后一个必须是空字符,(char *)0说明0是一个数字,转换成字符类型的指针也就是空字符
fprintf(strerr,"shell:could not exec %s:%s\n",buf,strerror(error));\\fprintf最少可以有三个参数,第一个是指定的流,第二个是打印,第三个是或者说第N个是变量对应的第二个参数中%n,%s等等,因为是打印到标准错误流中,但是并没有出错所以如果运行正常就不会报错
exit(EX_UNAVAILABLE);
if((pid=waitpid(pid,&status,0)) < 0){ //waitpid顾名思义就是等待子进程结束,但是waitpid和wait不同的是wait会阻塞此进程且不能选择pid,waitpid可以选择pid告诉内核要等他,且可以设置非阻塞状态,&status这个地址如果是null说明父进程不关心子进程,如果pid设置为-1那么代表监听所有子进程,大于1就是指定子进程,这个函数的返回值小于0当然是错误,如果等于0说明子进程在运行我干别的事情(非堵塞)只有当子进程挂了我再回收,如果没有回收就成为僵尸进程了
fprintf(stderr,"shell,waitpid error:%s\n",strerror(errno));
}
}
}
}
simple-ls
code
#include <sys/types.h>
#include <errno.h>
#include <dirent.h> //此头文件是posix定义一系列处理目录的函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
main(int argc,* argv[]){
DIR *dp; //DIR定义于dirent.h头文件中,是一个目录流,可以和文件流一样看待
struct dirent *dirp;
if (argc != 2){ //我们定义的格式是./xx DIR_NAME,所以是argc是2
fprintf(strerr,"usage:%s dir_name\n",argv[0]);
}
if((dp = opendir(argv[1]) == NULL)){ //使用opendir第一个参数也就是文件,给DIR指针,如果DIR指针等于NULL说明打开失败
fprintf(streer,"",uable to open%s:%s\n,argv[1],strerror(errno));
exit(EXIT_FAILURE)
}
while((dirp = readdir(dp)) != NULL){ //readdir读取一个目录流,第一次读取这个目录第一个子文件然后返回dirent结构
printf("%s"\n,dirp->d_name);
}
(void)closedir(dp);//关闭流,前面加上void也是避免warning
return EXIT_SUCCESS;
}
知识点总结
dirent.h定义了一系列关于dir操作的函数和结构
DIR *就是结构之一,可以把他看成"目录流",里面成员包含fd和缓冲区和file流一样
dirent结构成员如下,我们可以通过readdir函数读取流将所有信息填充进dirent结构
ino_t d_ino //File Serial number
char d_name //File string of entry
首先File serial number就是和inode number一样,inode number指向inode data struct,其包含文件的元数据,File serial number也是的,指向file相关的元数据,记住inode data struct里面没有文件名这么做是为了让多个文件指向inode。
stream和fd的区别
stream包含已经定义的FILE *和在头文件dirent.h定义的DIR流,他是fd的高度抽象,fd太过于偏向底层
The stream interface treats all kinds of files pretty much alike
fd呢就太底层了
像FILE* DIR *之类的流包含了一个fd和一个缓冲区,如果写的时候突然崩溃了,缓冲区里面还有,如果没有缓冲区,写了多少就是多少,丢掉的都没了
而且流提供了非常多强大的函数。
比较exec {} COMMAND ;和xarg
这里有一个非常有意思的问题find中exec和xarg谁效率更高?
先说结论xarg更高
在阐述原理之前我们要先了解几个概念
- clock time
程序运行开始到结束花费的时间
- system time
程序在kernel space所花费的时间(调用kernel space的代码运行了多久)
- user time
程序在user space所花费的时间(程序在userspace的代码运行了多久)
这三个时间我们总是认为system time+user time = clock time
上述公式大部分情况下是错误的
因为在user space到system time的时候上下文要切换,cpu寄存器要保存当前userspace的指令,切换到kernelspace指令,这需要时间,还有如果多进程那么进程间切换也要保存上下文比如全局变量,等等,最要命的I/O等待的时候进程被阻塞,这个时间既不属于system time也不属于user time,但是还是算进了clock time,这时候我们来实际测量这2个命令
以下2条命令都是ls空文件
root@workstastion:#time find /etc -type f -size 0c -exec ls {} \; //time后面直接加上command就可以测算时间
/etc/dictionaries-common/ispell-default
/etc/subgid-
/etc/.pwd.lock
/etc/security/opasswd
/etc/apparmor.d/local/usr.sbin.sssd
/etc/apparmor.d/local/usr.lib.libreoffice.program.soffice.bin
/etc/apparmor.d/local/usr.sbin.cupsd
/etc/apparmor.d/local/lsb_release
/etc/apparmor.d/local/sbin.dhclient
/etc/apparmor.d/local/nvidia_modprobe
/etc/apparmor.d/local/usr.bin.evince
/etc/apparmor.d/local/usr.lib.snapd.snap-confine.real
/etc/apparmor.d/local/usr.lib.libreoffice.program.senddoc
/etc/apparmor.d/local/usr.lib.libreoffice.program.oosplash
/etc/apparmor.d/local/usr.lib.libreoffice.program.xpdfimport
/etc/apparmor.d/local/usr.bin.man
/etc/apparmor.d/local/usr.sbin.tcpdump
/etc/apparmor.d/local/usr.sbin.cups-browsed
/etc/apparmor.d/local/usr.sbin.rsyslogd
/etc/sensors.d/.placeholder
/etc/subuid-
/etc/newt/palette.original
real 0m0.032s
user 0m0.015s
sys 0m0.016s
root@workstastion:/proc# time find /etc -type f -size 0c | xargs ls
/etc/apparmor.d/local/lsb_release /etc/apparmor.d/local/usr.sbin.cupsd
/etc/apparmor.d/local/nvidia_modprobe /etc/apparmor.d/local/usr.sbin.rsyslogd
/etc/apparmor.d/local/sbin.dhclient /etc/apparmor.d/local/usr.sbin.sssd
/etc/apparmor.d/local/usr.bin.evince /etc/apparmor.d/local/usr.sbin.tcpdump
/etc/apparmor.d/local/usr.bin.man /etc/dictionaries-common/ispell-default
/etc/apparmor.d/local/usr.lib.libreoffice.program.oosplash /etc/newt/palette.original
/etc/apparmor.d/local/usr.lib.libreoffice.program.senddoc /etc/.pwd.lock
/etc/apparmor.d/local/usr.lib.libreoffice.program.soffice.bin /etc/security/opasswd
/etc/apparmor.d/local/usr.lib.libreoffice.program.xpdfimport /etc/sensors.d/.placeholder
/etc/apparmor.d/local/usr.lib.snapd.snap-confine.real /etc/subgid-
/etc/apparmor.d/local/usr.sbin.cups-browsed /etc/subuid-
real 0m0.007s
user 0m0.002s
sys 0m0.006s
明显的xargs比exec快了接近5倍,为啥呢
因为exec运行了N次,这个N取决于前面find找到文件的次数,所以多个进程上下文切换就那么慢 ,而xargs运行了1次他是把前面find的结果通过管道当成结果给xargx再运行ls
simple-cat
code
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUFFSIZE 32768
int
main(int argc,char *argv[]){
int n;
char buf[BUFFSIZE];
(void)argc;
(void)argv;
while((n = read(STDIN_FILENO,buf,BUFFSIZE))>0){ //使用read函数从fd中读取,这个fd可以是管道,可以是标准输入(不是stdin,stdin是流)等等
if(write(STDOUT_FILENO,buf,n) != n){
fprintf(stderr,"Unable to write:%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
}
if(n<0){ //表示read失败,read失败会返回小于0的数
fprintf(stderr,"Unable to read:%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
return(EXIT_SUCCESS);
}
知识点总结
上面的程序主要用到2个程序一个是read和write,我们先说总结,如果函数是以f开头(比如fgets)的都是操作流,如果是像read和write一样都是操作fd,这是读取的2种方法
read函数从fd STDIN_FILENO这个文件描述符种获取内容写入read的第二个参数buf种,然后指定buf 的大小为BUFFSIZE;我们主要讨论STDIN_FILENO这个fd他是代表键盘等输入设备抽象成的文件fd,STDIN_FILENO的fd一般为0,所以读取会从我们的键盘读取,然后read的返回值是他读取了多少个字节,read结束必须要他遇到EOF,(ctrl+D就是EOF,ctrl+C是给一个interrupt signal)
write和read相反,是通过fd去写,STDOUT_FILENO是标准输出的fd比如屏幕什么之类的,STDOUT_FILENO一般为1,write成功会返回自己写了几个字节,失败参考如下(和read一样)
If write() is interrupted by a signal before it writes any data, it shall return -1 with errno set to [EINTR].
If write() is interrupted by a signal after it successfully writes some data, it shall return the number of bytes written.
If the value of nbyte is greater than {SSIZE_MAX}, the result is implementation-defined.
EXIT_FAILURE和EXIT_SUCCESS都定义于stdli.h,一个表示非0,一个表示0,总是用于exit的时候
这个程序怎么使用呢?我们是不是要先给他一个标准输入才能打印,怎么给这个程序一个标准输入?用<比如 Simeple-cat < /etc/hosts
这样就是给了一个标准输入给程序
我们这里read和write都是有buffersize限制,如果buffsize不够大可能需要多次read,多次write的syscall这样效率会降低(参考find的exec和xargs),那么我们把buffsize的大小调的非常非常大会不会增加效率?
simple-cat2
code
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,*argv[]){
int c;
(void)argc;
(void)argv;
while((c = getc(stdin)) != EOF){ //getc和fget一样从流中读取,read从fd中读取,(c = getc(stdin))赋值语句返回c的值
if(putc(c,stdout) == EOF){
fprintf(stderr,"Unable to write%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
}
if(ferror(stdin)){
fprintf(stderr,"Unable to read:%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
return(EXIT_SUCCESS);
}
知识点总结
和上个程序差不多但是这里面主要函数是操作的流,而上一个程序操作的是fd,我们讲过操作流的函数前面都以f开头比如fprintf,fgets等等,这里getc和putc也是操作流,而且他们不像fgets有缓冲区,getc直接读取标准输入流和标准输出流
getc的返回值非常有趣,因为他是一个一个读取字符,所以读取一个字符返回这个字符的int类型,最后读完或者发成错误就返回EOF(-1)
putc也是指定一个int类型的字符,在指定一个流就输出,记住这个字符类型一定是int的比如我们getc的返回值,如果出错就返回EOF
ferror是测试指定流的错误标识就像echo $?不过这个是对特定流的,也一样0为正常非0为不正常,注意每一次调用输入输出ferror都不一样
ferror定义域stdio.h中
simple-shell2
code
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <sysexits.h>
#include <sys/types.h>
char *
getinput(char *buffer,size_t buflen,){
printf("$$");
return fgets(buffer,buflen,stdin);
}
void
sig_int(int signo){
printf("\ncaught SIGINT (signal #%d)!\n$$",signo);
(void)fflush(stdout);//刷新流中的缓冲区(我们知道流是fd的高级封装,流中不光包含fd也有缓冲区)
}
int
main(int argc,char *argv[]){
char buf[BUFSIZE];
pid_t pid; //<sys/types.h>中typedef short pid_t
int status;
(void)argc;
(void)argv;
if(signal(SIGINT,sig_int) == SIG_ERR){ //signal函数用于处理信号,SIGINT是信号码,代表signal interrupt,sig_int是我们写的函数,这里传参是一个函数指针,这个函数的意思就是只要我们碰到SIGINT信号我们就跳转到sig_int函数中,由这个函数处理
fprintf(stderr,"signal error:%s\n",strerror(errno));
exit(1);
}
while(getinput(buf,BUFSIZE)){
buf[strlen(buf) -1 ] = '\0'; //消掉最后读入的换行符
if((pid = fork()) == -1){
fprintf(stderr,"Unable to fork:%s",strerror(errno));
continue;
}else if(pid == 0){ //child
execlp(buf,buf,(char *)0);
fprintf(stderr,"Shell could't exec %s:%s",buf,strerror(errno));
exit(EX_UNAVAILABLE);
}
if((pid = waitpid(pid,&status,0)) > 0){
fprintf(stderr,"Could't wait pid%s",strerror(errno));
}
}
exit(EX_OK);
}
知识点总结
为什么有了simple-shell1还有来个simple-shell2,因为我们在shell的时候对应突发的信号无能为力,比如进入了shell我们输入Ctrl+c(interrupt)或者Ctrl+d(EOF),所以simple-shell2不受信号干扰
I/O操作
open/close
code(openex)
/*
此代码演示用open创建一个文件(不close)
再用open打开一次上述文件,查看其fd是否累加
尝试使用open的O_EXCL选项查看在使用open一个已存在的文件时是否报错,并查看close的返回值
尝试使用open的O_TRUNC,查看是否将已存在的文件长度截为1(清除内容)
*/
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h> //open函数
#include <stdlib.h> //system()函数
#include <string.h>
#include <unistd.h>
#ifndef SLEEP
#define SLEEP 10
#endif
void
creatFile(){ //没有close,所以第一次调用fd可能是3或者4,如果再调用这个函数fd会累加
int fd;
printf("Checking if ./newfile is exists...\n");
system("ls -l ./newfile");
printf("try to create ./newfile with O_CREAT|O_RDONLY \n");
if((fd = open("newfile",O_CREAT|O_RDONLY,S_IRUSR|S_IWUSR)) < 0){
fprintf(stderr,"unable to create newfile:%s",strerror(errno));
exit(EXIT_FAILURE);
}
printf("newfile created,fd is %d\n",fd);
}
void
failExclFileCreation(){
int fd;
printf("Checking if ./newfile is exists...\n");
system("ls -l ./newfile");
printf("try to create ./newfile with O_CREAT|O_RDONLY|O_EXCL\n");
if((fd = open("newfile",O_CREAT|O_RDONLY|O_EXCL,S_IRUSR|S_IWUSR)) == -1){
fprintf(stderr,"unable to create newfile:%s\n",strerror(errno));
}
if(close(fd) == -1){
fprintf(stderr,"closing failed %s\n",strerror(errno));
}
void
failOpenNonexistingFile(){
int fd;
printf("try to open no-existfile with O_RDONLY\n");
if((fd = open("noexistfile",O_RDONLY)) == -1 ){
fprintf(stderr,"unable to open a no-exitfile%s\n",strerror(errno));
}
(void)close(fd);
}
void
openFile(){
int fd;
printf("Try to open './openex.c' with O_RDONLY..\n");
if((fd = open("./openex.c",O_RDONLY)) == -1){
fprintf("unable to open ./openex.c:%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
printf("./openex.c opened fd is %s\n",fd);
if(close(fd) == 0){
printf("openex.c close again\n");
}
}
void
truncateFile(){
int fd;
system("cp openex.c newfile");// cp openex.c成newfile
printf("copid openex.c to newfile\n");
system("ls -l newfile");
printf("try to open newfile with O_RDONLY|O_TRUNC\n");
if((fd = open("newfile",O_RDONLY|O_TRUNC)) == -1){ //如果newfile存在O_TRUNC将其的大小置为0,也就是清空内容
fprintf(stderr,"unable to open newfile:%s",strerror(errno));
exit(EXIT_FAILURE);
}
printf("./newfile is opend fd is %d",fd);
prinff("newfile trunc -- see ls -l newfile\n");
system("ls -l newfile");
(void)close(fd);
}
int main(int argc,char *argv[]){
(void)argc;
(void)grgv;
createFile();
system("ls -l newfile");
printf("\n");
sleep(SLEEP);
createFile();
system("ls -l newfile");
printf("\n");
sleep(SLEEP);
failExclFileCreation();
printf("\n");
sleep(SLEEP);
openFile();
printf("\n");
sleep(SLEEP);
failOpenNonexistingFile();
printf("\n");
sleep(SLEEP);
truncateFile();
return 0;
}
原理
后弦open的第一个参数是filename,第二个参数是以什么权限打开还是,O_CREAT代表没有文件就创建,此选项可以接第三个参数S_IRUSR,代表READ User权限也就是100
在一个进程中fd是累加的,不管前一个fd有没有close,close的时候有可能出错因为被中断或者open的时候加上了O_EXCL选项,这个选项如果被open的file存在就报错(如果不加O_EXCL不报错,说明可以重复open),然后fd就返回-1,close(-1)就报错。。。
read/write
code
#include <erno.h>
#include <fcntl.h> //open函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUFFSIZE 64
#define COMMENT "\n/*just another comment*/\n"
#define SOURCE "./rwex.c"
int main(void){
int fd,n;
int len;
char buf[BUFFSIZE]; //write和read用的buf
if((fd = open(SOURCE,O_APPEND|O_RDWR)) == -1){ //一但open加上了O_APPEND参数那么write的时候就从fd指向文件的最后一位开始写
fprintf(stderr,"unable to open:%s",strerror(errno));
exit(EXIT_FAILURE);
}
if((n = read(fd,buf,BUFFSIZE)) > 0){
if(write(STDOUT_FILENO,buf,n) != n){
fprintf(stderr,"unable to write:%s",strerror(errno));
exit(EXIT_FAILURE);
}
}else if(n == -1){
fprintf(stderr,"unable to read from %s:%s",SOURCE,strerror(errno));
exit(EXIT_FAILURE);
}
printf("now we write someting when open add flag O_APPEND\n");
len = sizeof(COMMENT) - 1; //sizeof是包含\0所以减去1是减去\0
if(write(fd,COMMENT,len) != len){
fprintf(stderr,"unable to write %s:%s",COMMENT,strerror(errno));
exit(EXIT_FAILURE);
}
(void)close(fd);
return EXIT_SUCCESS;
}
原理
首先open如果加上了O_APPENDflag那么后续对open返回的fd进行write操作时是从fd指向文件末端进行写入
dup/dup2
code(redir.c)
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#define STDOUT_MSG "A Message to stdout\n"
#define STDERR_MSG "A Message to stderr\n"
void
writeBoth(const char *mark){
int len,marklen;
marklen = strlen(mark);
if(write(STDOUT_FILENO,mark,marklen) != marklen){
perror("unable to write marker to stdout"); //将里面的字符串发送到stderr流中去,定于与stdio.h
exit(EXIT_FAILURE); //定义于stdlib.h
}
len = strlen(STDOUT_MSG);
if(write(STDOUT_FILENO,STDOUT_MSG,len) != len){
perror("unable to write to stdout");
exit(EXIT_FAILURE);
}
if(write(STDERR_FILENO,STDERR_MSG,len) != len){
perror("unable to write to stderr");
exit(EXIT_FAILURE);
}
}
int
main(int argc,char * argv[]){
(void)argc;
(void)argv;
writeBoth("before dup2\n");
if(dup2(STDOUT_FILENO,STDERR_FILENO) < 0){
perror("unable to redirect stderr to stdout");
exit(EXIT_FAILURE);
}
writeBoth("after dup2\n");
}
运行结果
root@workstastion:/apue/course/02# gcc redir.c -o redir.o
root@workstastion:/apue/course/02# ./redir.o
before dup2
A message to stdout.
A message to stderr.
after dup2
A message to stdout.
A message to stderr.
root@workstastion:/apue/course/02# ./redir.o 2> /dev/null
before dup2
A message to stdout.
after dup2
A message to stdout.
A message to stderr.
root@workstastion:/apue/course/02# ./redir.o > redir
A message to stderr.
root@workstastion:/apue/course/02# cat redir
before dup2
A message to stdout.
after dup2
A message to stdout.
A message to stderr.
原理
我们看以上运行结果,首先看第一个直接运行
通过第一次运行
我们知道了write给STERR_FILENO的时候也是直接默认写入屏幕,这一点和STDOUT_FILENO一样
第二次运行
把标准输出的打印出来,把标准错误的输出到/dev/null这个特殊文件中,为什么after dup2后stderr也可以打印?因为dup2(STDOUT_FILENO,STDERR_FILENO),把STDOUT的fd复制给了STDERR的fd,所以after dup2后,STDERR不再是标准的STDERR,而是STDOUT,所以可以直接打印
第三次运行
把所有stdout都给了redir这个文件,打印出来的只是stderr的,所以我们cat redir这个文件可以看到所有stdout输出
>和2>的区别
在linux中 > 和 2> 分别代表 stdout 和 stderr 的内容重定向到某文件中
权限操作
stat
simple-ls-stat版本(code)
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
char *
getType(const struct stat sb) {
if (S_ISREG(sb.st_mode))
return "regular file";
else if (S_ISDIR(sb.st_mode))
return "directory";
else if (S_ISCHR(sb.st_mode))
return "character special";
else if (S_ISBLK(sb.st_mode))
return "block special";
else if (S_ISFIFO(sb.st_mode))
return "FIFO";
else if (S_ISLNK(sb.st_mode))
return "symbolic link";
else if (S_ISSOCK(sb.st_mode))
return "socket";
else
return "unknown";
}
int
main(int argc, char **argv) {
DIR *dp;
struct dirent *dirp;
if (argc != 2) {
fprintf(stderr, "usage: %s dir_name\n", argv[0]);
exit(EXIT_FAILURE);
}
if ((dp = opendir(argv[1])) == NULL) {
fprintf(stderr, "can't open '%s'\n", argv[1]);
exit(EXIT_FAILURE);
}
if (chdir(argv[1]) == -1) {
fprintf(stderr, "can't chdir to '%s': %s\n", argv[1], strerror(errno));
exit(EXIT_FAILURE);
}
while ((dirp = readdir(dp)) != NULL) {
struct stat sb;
char *statType;
printf("%s: ", dirp->d_name);
if (stat(dirp->d_name, &sb) == -1) {
fprintf(stderr, "Can't stat %s: %s\n", dirp->d_name,
strerror(errno));
statType = "unknown";
} else {
statType = getType(sb);
}
if (lstat(dirp->d_name, &sb) == -1) {
fprintf(stderr,"Can't lstat %s: %s\n", dirp->d_name,
strerror(errno));
continue;
} else if (S_ISLNK(sb.st_mode)) {
printf("symlink to ");
}
printf("%s\n", statType);
}
(void)closedir(dp);
return EXIT_SUCCESS;
}
setuid/getuid
myuid(code)
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
void
printUids(){
uid_t ruid = getuid();
uid_t euid = geteuid();
printf("my effective uid is %d\n",euid);
printf("my real uid is %d\n",ruid);
}
int
main(){
printUids();
uid_t ruid = getuid();
uid_t euid = geteuid();
if(ruid = euid){
exit(EXIT_SUCCESS)
}
printf("\nLet's drop elevated privs and set our effective uid to our real uid.\n");
printf("Calling 'seteuid(%d)'...\n", ruid);
if (seteuid(ruid) < 0) {
fprintf(stderr, "Unable to seteuid(%d): %s\n", ruid, strerror(errno));
} else {
printUids();
}
printf("\nBecause we used seteuid(%d) and not setuid(%d), we can get back our earlier euid %d.\n", ruid, ruid, euid);
printf("Calling 'seteuid(%d)'...\n", euid);
if (seteuid(euid) < 0) {
fprintf(stderr, "Unable to seteuid(%d): %s\n", euid, strerror(errno));
} else {
printUids();
}
printf("\nNow let's try to 'setuid(%d)'...\n", ruid);
if (setuid(ruid) < 0) {
fprintf(stderr, "Unable to setuid(%d): %s\n", ruid, strerror(errno));
} else {
printUids();
}
printf("\nNow let's try to gain back our effective uid %d...\n", euid);
printf("Calling 'seteuid(%d)'...\n", euid)
if (seteuid(euid) < 0) {
fprintf(stderr, "Unable to seteuid(%d): %s\n", euid, strerror(errno));
}
printUids();
return EXIT_SUCCESS;
}
}
原理
首先我们看文件的权限
root@workstastion:/tmp# ls -l myuids.o
-rw-r--r-x 1 root root 17280 1月 25 21:40 myuids.o
直接用属主执行文件
root@workstastion:/tmp# ./myuids.o
My effective uid is: 0
My real uid is: 0
说明执行文件时我们是什么身份ruid就是什么身份(因为shell是zhr所以执行文件时继承了zhr这个uid),euid和ruid一般都一样,euid用于应对内核权限审查(除非设置了setuid位)
切换用户执行(前提要对文件有权限,如果没有权限执行都执行不了更别说ruid和euid)
root@workstastion:/tmp# su - zhr
zhr@workstastion:~$ cd /tmp
zhr@workstastion:/tmp$ ./myuids.o
My effective uid is: 1000
My real uid is: 1000
zhr@workstastion:/tmp$
和上述的结果差不多,因为shell是zhr所以执行程序的时候ruid变成了zhr,euid也是zhr,euid再强调一遍用于应对内核权限审查,只有当设置setuid位的时候euid才会变
切回root用户给文件上setuid位,并且切回zhr用户执行
zhr@workstastion:/tmp$ exit
logout
root@workstastion:/tmp# chmod u+s myuids.o
root@workstastion:/tmp# su - zhr
zhr@workstastion:~$ cd /tmp
zhr@workstastion:/tmp$ ./myuids.o
My effective uid is: 0
My real uid is: 1000
Let's drop elevated privs and set our effective uid to our real uid.
Calling 'seteuid(1000)'...
My effective uid is: 1000
My real uid is: 1000
Because we used seteuid(1000) and not setuid(1000), we can get back our earlier euid 0.
Calling 'seteuid(0)'...
My effective uid is: 0
My real uid is: 1000
Now let's try to 'setuid(1000)'...
My effective uid is: 1000
My real uid is: 1000
Now let's try to gain back our effective uid 0...
Calling 'seteuid(0)'...
Unable to seteuid(0): Operation not permitted
My effective uid is: 1000
My real uid is: 1000
看第一条打印euid已经变成文件属主的uid 0 了,因为我们设置了setuid位执行文件的时候可以将euid变成属主的uid规避内核的权限检查,但是ruid还是zhr的uid
看第二条打印seteuid因为此时的权限是root想怎么设置就怎么设置所以euid设置成1000没问题(如果没有root权限只能设置成euid或者ruid反正没设置setuid位的时候都一样)
看第三条打印我们seteuid为之前的euid没问题直接设置
看第四条打印我们将调用setuid为ruid也没问题,因为从euid的权限审查来说我们是root可以进行这个操作,就算不是root也可以将ruid变成euid和ruid,注意在改变ruid的时候euid也会跟着变,所以euid也变成了ruid,并且euid不能再回去了
看第五条我们已经不能将euid设置成之前setuid位的uid了
access
使用access函数确定是否有权限打开某个文件(code)
#include <fcntl.h> //open()函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc, char *argv[]){
if(argc != 2){
fprintf(stderr,"%s:usage:%s filename\n",argv[0],argv[0]);
exit(EXIT_FAILURE);
}
if(access(argv[1],R_OK) == -1)
printf("access error for %s\n",argv[1]);
else
printf("access ok for %s\n",argv[1]);
if(open(argv[1],O_RDONLY) == -1)
printf("open error for %s\n",argv[1]);
else
printf("open ok for %s\n",argv[1]);
exit(EXIT_SUCCESS);
}
access对比的时ruid而不是euid如果编译后的文件加上了setuid位并且owner时root则access的时候对一些owner为root的失败,但是open的确可以open,因为setuid位
chmod
chmod(code)
chmod非常简单,我们平常在使用的时候就用了非常多次,chmod系统调用也是非常简单
#include <sys/types.h> //chmod函数
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int
main(){
struct stat sbuf;
if(stat("file",&sbuf) == -1 ){
perror("can't stat file");
exit(EXIT_FAILURE);
}
if(chmod("file",(sbuf.st_mode &~ S_IRUSR | S_ISGID))== -1){
perror("cant't chmod file");
exit("EXIT_FAILURE");
}
if(chmod("file",S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) == -1 ){ //rw-r--r--
perror("can't chmod file");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
这里的&~
代表关闭某个位,这个位就是S_IRUSR,然后|
是开启某个位也就是开启S_ISGID,
为什么这个权限的名字这么畸形?因为unix历史原因。。。
S=stat
I=inode
chown
chown(code)
chown也非常简单
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> //chown系统调用
#define ZHR 1001
#define USER_GID 100
#define TTY_GID 4
void
mian(const char *file ,int uid ,int gid){
if(chown(file,uid,gid) < 0){
fprintf(stderr,"Unable to chown(%s,%d,%d):%s\n",file,uid,gid,strerror(errno));
}else{
printf("Successfully chownd %s to %d:%d",file,uid,gid);
}
}
int
main(int argc,char *argv[]){
if(argc != 2){
fprintf(stderr,"Usage:%s file\n",argv[0]);
exit(EXIT_FAILURE);
}
mychown(argv[1],getuid(),-1); //传递-1代表不改变这个uid或者gid
mychown(argv[1],ZHR,-1);
mychown(argv[1],-1,USER_GID);
mychown(argc[1],-1,TTY_GID);
exit(EXIT_SUCCESS);
}
umask
umask(code)
我们创建一个文件的权限是666(8进制)创建一个目录的权限是777(8进制),如果我们的umask不设置就是不屏蔽某个位,设置勒就在创建目录的时候或者创建文件的时候用umask按照777或者666进行屏蔽(&~
)
在我们写工程级别的代码的时候umask可以直接设定为数字,也可以设定为umask(S_IRUSR|S_IWUSR|S_IXUSR)
这种方式指定屏蔽那个位
#include <sys/stat.h> //umask syscall
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void
myOpen(const char *path){
int fd;
if(fd = open(path,O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR|S_IWUSR|S_IXUSR
S_IRGRP|S_IWGRP|S_IXGRP
S_IROTH|S_IWOTH|S_IXOTH) == -1){
fprintf(stderr,"Unable to create %s:%s\n",path,strerror(errno));
exit(EXIT_FAILURE);
}
(void)close(fd)
}
int
main(){
myOpen("file1");
umask(0);
myOpen("file2");
umask(S_IRGRP | S_IROTH | S_IWOTH);//这个写法非常的明了,该屏蔽谁直接写出来
myOpen("file3");
return EXIT_SUCCESS;
}
软链接,硬链接
wait-unlink(code)
这个程序主要是测试我们的硬链接在链接数为0的时候会不会自己删除(不会,因为我们有一个open打开了他,当open结束他也就没了)
这里还有一个妙用,如果我们要暂停程序可以用(void)getchar()
,因为getchar遇到回车挥着EOF就会结束,如果我们不输入他就会在标准输入里面一直等着你输入,也就是卡着,然后输入后返回相应字符,但是我们前面有一个void强制将其转换成void也就是啥也没有,避免编译器报错
#include <fcntl.h> //link函数
#include <stdio.h> //perror(string)函数,里面的string参数会先打印到标准错误的流中,再打印errno变量里面的错误
#include <stdlib.h> //system()
#include <unistd.h> //chdir()
void
runDf(){
printf("\nAvailable space is now:\n");
if(system("df .") != 0){
perror("unable to run df(1)");//里面的string参数会先打印到标准错误的流中,再打印errno变量里面的错误
exit(EXIT_FAILURE);
}
int
main(void){
int fd;
//chdir to /var/tmp
if(chdir("/var/tmp/") == -1){
perror("unable to cd to /var/tmp");
exit(EXIT_FAILURE);
}
//先看一下当前的磁盘容量
runDf();
//暂停以下等待输入回车
(void)getchar();
printf("Creating a 500M file...\n");
if(system("dd if=/dev/zero of=foo bs=1024k count=500 > /dev/null") !=0 ){
perror("unable to dd a new file");
exit(EXIT_FAILURE);
}
runDf();
(void)getcahr();
printf("\nLink bar to foo ...\n");
//创建硬链接
if(link("foo","bar") != 0){
perror("unable to hard link");
exit(EXIT_FAILURE);
}
runDf();
(void)getchar();
//open foo让进程占有她
if(fd = open("foo",O_RDWR)){
perror("unable to open bar");
exit(EXIT_FAILURE);
}
printf("\nopend foo on fd");
(void)getchar();
if(unlink("foo")<0){
perror("error unlinking");
exit(EXIT_FAILURE);
}
printf("\nok,foo unlinking");
printf("Disk space not free'd since 'bar' still exist\n");
(void)system("ls -li foo bar"); //我们期望这里报错
runDf();
(void)getchar();
printf("\nOk now unlinking bar\n");
if(unlink("bar")<0){
perror("error unlink");
exit(EXIT_FAILURE);
}
printf("now bar is unlinked\n");
(void)getchar();
printf("Disk space not free'd since I still have fd#%d open...\n",fd);
printf("\nRunning 'ls -li foo bar':\n");
(void)system("ls -li foo bar");
runDf();
(void)getchar();
printf("Now closing fd#%d...\n", fd);
/* Closing the file descriptor after having
* unlinked all references to the 500M file
* finally frees the disk space. */
(void)close(fd);
printf("\n...and done. Disk space is freed now.\n");
runDf();
exit(EXIT_SUCCESS);
}
}
原理
说软链接和硬链接之前先了解文件系统基本的原理
首先一个磁盘的分区情况如下,每一个分区都代表一个文件系统
|---分区1---|---分区2---|......|---分区n---|
因为每一个分区都可以代表一个操作系统所以我们再细看一个分区的情况,
|---自举块---|---superblock---|---blockgroup1---|......|---blockgroupN---|
自举块就是引导,superblock上面已经介绍,后面都是blockgroup
因为每一个分区都有多个blockgroup所以我们细看每一个blockgroup
|---superblock(backup)---|---配置信息---|---inode bitmap---|---block bitmap---|---inode---|---block---|
inode bitmap是以二进制的方式看inode使用情况,block bitmap是以二进制的方式记录勒block使用了多少,后面的inode其实是非常多inode组成的一个组,也就是此分区所有inode放一起存储,后面的block就是此分区所有的block
我们这里要明白上述的inode是分区所有inode的一个集合,block是数据块和目录快的一个集合,看以下图
|---inode组---|---目录块---|---数据块---|---数据块---|---数据块---|......|---目录块---|---数据块---|---数据块---|......|
我们只是将数据块和目录块统称为block,其中目录快存放的是inode编号(对应前面的inode)和文件名的对应信息 |inode number|文件名|
,在某个目录下查找文件,先看目录块看这个文件名对应的inode number,再转到对应的inode中直接指向block,注意inode中都有一个文件计数器,记录有多少个目录块的条目指向它(链接数),如果计数器为0,代表删除文件(当然先检查打开该文件进程的个数如果为0再检查链接个数),所以删除一个目录块中的某条记录的syscall叫做unlink()而不是delete(),unlink也可以unlink一个不是链接的文件如果没有被占有他就被删除
这里又牵扯出2个概念,硬链接和软连接
硬链接:有不同的block,但是其block的目录块中的记录指向同一个inode
软链接:有不同的inode,但是block是同一个
所以删除软链接非常块,只需要改一下指针即可,但是删除硬链接就非常慢了,因为要清空磁盘
如何知道inode指向的是一个文件还是一个目录?看它的指向,如果指向的block是一个数据块那么就是文件,如果指向的是一个目录块那么他就是目录
,那么目录是如何寻址的呢?
我们创建一个空目录
mkdir test
此时我们的block中多了一个目录块假设其inode为2549,因为我们知道目录是一个目录包含另一个目录,所以test也被另一个目录包含,那么包含他的目录块假设inode为1267,这个1267指向的是一个目录块,块里面第一个记录是1267|.
代表本目录,第二个INODE|..
代表这个目录的上一级目录,第N个是2549|test
代表test这个目录,然后这个记录指向inode为2549,然后inode2549又指向block里的对应目录块
参考APUE P93
我们看一下link的系统调用
#include <fcntl.h>
#include <unistd.h>
int link(const char * path 1,const char * path2);
int linkat(int fd1,const char * path1,int fd2,const char * path2,int flags);
我们再看一下删除链接的系统调用
#include <fcntl.h>
#include <unistd.h>
int unlink(const char * path);
int unlinkat(int fd,const * path,int flags);
如果知道了上面的知识rename系统调用就好理解了,rename相当于将目录块的记录改名,如果rename的newfile存在那么就先将这个newfile的记录删除再将oldfile的记录改为newfile,所以我们有了以下的代码
rename
rename(code)
这个代码就是将rename实现了一遍,但是有几个细节我们需要注意
#include <errno.h>
#include <stdio.h> //fprintf,rename函数
#include <stdlib.h> //exit函数
#include <string.h>//strerror函数
#include <unistd.h>
int
main(int argc,char*argv[]){
if(argc != 3){
fprintf(stderr,"Usage: %s from to\n",argv[0]);
exit(EXIT_FILEURE);
}
if(rename(argv[1],argv[2])< 0){
fprintf(stderr,"Unable to rename '%s' to '%s': %s\n",argv[1], argv[2], strerror(errno));
exit(EXIT_FILEURE);
}
return EXIT_SUCCESS;
}
符号链接
lns(code)
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> //symlink函数
int
main(int argc,char * argv[]){
if(argc != 3){
fprintf(stderr,"usage:%s source traget\n",argv[0]);
exit(EXIT_FAILURE);
}
if(symlink(argv[1],argv[2]) < 0){
fprintf(stderr,"unable to symlink '%s' to '%s':%s",argv[1],argv[2],strerror(errno));
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
原理
首先符号链接就像我们的软链接,它没有自己的block块,只有一个inode,所以删除的时候删除的只有inode,所以速度比硬链接快,还有就是我们open,stat,chmod,chown等syscall操作的只是符号链接指向的文件,而不是符号链接本身,如果想操作符号链接本身就在这些syscall前面加上l比如lopen(貌似没有。。。symbolic link只是一些元数据获取他的fd貌似没啥意义,真的要获取就用lstat获取symbolic link本身的元数据,或者readlink查查看symbolic link的内容),lstat,lchmod,lchown
symlink可以对一个不存在的文件进行指定比如file1不存在,但是我们可以对file1进行symlink到另一个文件 ln -s file1 file2
不会报错,但是当我们给file填写数据后file后file1自动也创建了,所以symlink可以连接所有的东西,毕竟他只是一个inode号,没有block块
我们看一下关于symbolic link的syscall
建立symbolic link的syscall
#include <unistd>
int symlink(const char * traget,const char * linkpath);
查看symbolic link本身的syscall
#include <unistd.h>
ssize_t readlink(const char * pathname,char *buf,size_t bufsize);
//buf不能为空
目录操作
cd(code)
#include <sys/param.h> //MAXPATHLEN宏
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int
main(int argc,char *argv[]){
char buf[MAXPATHLEN];
if(argc != 2){
fprintf(stderr,"%s:need a directory!\n",argv[0]);
exit(EXIT_FAILURE);
}
if(chdir(argv[1]) == -1 ){
fprintf(stderr,"%s:unable to chdir to %s:%s\n",argv[0],argv[1],strerror(errno));
exit(EXIT_FAILURE);
}
printf("ok!now dir is:%s",getcwd(buf,MAXPATHLEN)); //getcwd成功就返回一个字符指针(字符串)
exit(EXIT_SUCCESS);
}
上面的函数运行后发现并没有改变目录,这是为什么?因为我们运行这个程序会由当前的shell fork一个新的子进程程序在这个子进程上运行,chdir change的也是子进程的工作目录,当程序执行完毕就退出这个子进程返回父进程shell,所以父进程的getcwd没有变
原理
如何创建目录?我们都知道mkdir,那么相关的syscall呢也是的,我们现在来看他的函数原型
#include <sys/stat.h>
#include <fcntl.h>
int mkdir(const char * path,mode_t mode);
int mkdirat(int fd,const char * path,mode_t mode);
注意这个mode_t mode和open的第三个参数一样是用来设置权限的比如
S_IRUSR|S_IWUSR|I_XUSR
删除目录的函数原型如下
#include <unistd.h>
int rmdir(const char *path);
如果目录里面什么都没有就清零这个目录的stat结构体中的st_nlink(硬链接数量),为0再加上没有程序占有就删除文件
读取目录的内容
#include <dirent.h>
DIR * opendir(const char *path);
DIR * fdopendir(int fd);
该函数打开一个目录返回一个指针,这个指针指向DIR流,DIR流的结构是一个内部结构,光打开目录将目录信息传送到DIR流中还不够,我们要根据这个流读取信息到专门的结构struct dirent
中,这个才是正确的打开方式
#include <dirent.h>
struct dirent *readdir(DIR *dp);
readdir函数专门读一个dir流,将里面的信息返回到dirent中,我们的目录信息都存储再dirent这个结构中,其结构如下
struct dirent
{
long d_ino; /* inode number 索引节点号 */
off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
unsigned short d_reclen; /* length of this d_name 文件名长 */
unsigned char d_type; /* the type of d_name 文件类型 */
char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
}
我们知道pwd是列出当前目录的命令那么相应的系统调用如下
#include <unistd.h>
char * getcwd(char *buf,size_t size);
getcwd成功就返回一个字符指针(字符串)
一般来说poxie的系统调用涉及到buf都要确定buf大小
可以列出当前目录我们也可以切换当前工作目录
#include <unistd.h>
int chdir(const char *path);
在我们创建一个目录的时候在目录下又创建了多个空文件,目录的大小会先分配512个字节装载新增文件的inode和大小和文件名,当文件数量大于512个字节的时候再分配512个字节给目录,当我们删除文件的时候目录大小不会变,因为删除目录中的文件目录记录的元数据inode,文件名,大小不会被删除,只有我们在目录下创建新的文件才会覆盖原来被删除文件的元数据
以前还可以用hexdump或者od等命令查看目录本身内容(inode和包含文件名),但是自从08年后大部分*NIX发行版就屏蔽了这个功能
系统文件操作
这里专门讲述系统文件的syscall
getpw(code)
#include <assert.h> //断言
#include <err.h> //errx函数
#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h> //strtol函数
#include <string.h>
struct passwd *who(*char); //傻了这是一个函数原型,输入一个字符,返回一个passwd结构的指针,我他妈以为是什么骚操作...
void printAllEntries(void);
void printOnestruct(const struct passwd *pw);
struct passwd *
who(char *u){
struct passwd *pw;
long id;
char *ep;
if((pw = getpwnam(u)) != NULL){ /**通过用户名获取相应passwd的行传递
给pw指针,如果用户输入的是用户名就会返回指针,如果输入非法就返回NULL,
这里判断其合法,也就是输入的是有效用户名就直接返回**/
return pw;
}
/*如果用户输入的不是指针而是数字看这里*/
id = strtol(u,&ep,10) /*这个strtol有必要说一下,u代表待判断的参数,10
代表输入的合法值范围 10代表合法值为0~9,36代表合法值为0~9,a~z,当字符
合法时,‘0’,……‘9’依次被转换为十进制的0~9,‘a’,……‘z’依次被转换为十进
制的10~35,如果有不合法的都被存入ep这个字符串数组中,记住ep是字符串数
组*/
if(*u && !*ep && (pw = getpwuid(id))){ /*这里判断u不为空,ep(不标准
的输入字符)为空,getpwuid没毛病就返回pw,这里注意if(*ptr)里面一个指针
,指针如果为空代表NULL,linux代表NULL就是0,所以如果指针为空就是
if(0),相当于假直接不执行后续分支如果我们想让他为空就if(!*ptr),ptr是一
个空指针*/
return pw;
}
errx(EXIT_FAILURE,"%s:no such user",u); //err.h
return NULL;
}
void
printAllEntries(void){
struct passwd *pw;
errno = 0; //errno相当于echo $?,这里相当于给他初始化以下后面会用到错误代码
while( (pw = getpwent())!= NULL){ /*getpwent()这个函数定义在pwd.h中
其作用就是打开/etc/passwd这个文件(流方式打开),然后第一次读取第一行
数据(也就是第一个用户数据),然后返回给pw,第二次读取第二行(也就是第
二个用户数据)数据再返回给pw结构,直到passwd文件没有数据了就返回一个
null*/
printOneStruct(pw);
}
if(errno){ //errno看这个怎么出错了
fprintf(stderr,"%s:Unable to call getpwent(3):%s\n",getprogname(),strerror(errno));
exit(EXIT_FAILURE);
}
endpwent(0);
}
void
printOneStruct(const struct passwd *pw){
assert(pw);
printf(%s:%s:%d:%d:%s:%s:%s\n,pw->pw_name,pw->pw_passwd,pw->pw_uid,pw->pw_gid,pw->pw_gecos,pw->pw_dir,pw->pw_shell);
}
int
main(int argc,char **argv[]){
(void)setprogname(argv[0]);
if(argc > 2){
fprintf(stderr,"Usage:%s [name|uid]\n",getprogname());
exit(EXIT_FAILURE);
}
if(argc == 2){
printOneStruct(who(argv[1]));
}else{
printAllEntries();
}
return EXIT_SUCCESS;
}
原理
先看一下几个重要的系统调用
#include <pwd.h>
struct passwd * getpwnam(const char * name);
struct passwd * getpwuid(uid_t uid);
以上2个函数的功能都一样都是输入用户名/uid返回相应在passwd的一样,passwd每行数据由struct passwd
结构保存,其内容如下
struct passwd {
char *pw_name; /* username */
char *pw_passwd; /* user password */
uid_t pw_uid; /* user ID */
gid_t pw_gid; /* group ID */
char *pw_gecos; /* user information */
char *pw_dir; /* home directory */
char *pw_shell; /* shell program */
};
还有一个函数是用来返回整个passwd文件用的,如果我们用getpwnam
或者getpwuid
syscall返回整个passwd文件就要先知道所有用户名,所有uid,所以性能非常底下,因此这里介绍一个系统调用,先以流的方式打开passwd文件(这样我们访问就有偏移量每访问一次都会有记录),访问一次返回passwd文件一行(哪一行?就是从第一行开始,第二次返回第二行,直到最后一行完后返回NULL也就是0),这个函数就是getpwent
#include <pwd.h>
struct passwd *getpwent(void);
因为getpwent是流的方式打开passwd文件我们要关闭它,调用这个函数关闭流
#include <pwd.h>
void endpwent(void);
getpwent
的用法在上面列出来了,放在while中循环打印所有的passwd行
struct passwd *pw ;
while((pw = getpwent()) != NULL){
printf("%s:%s:%d:%d:%s:%s:%s\n",passwd->pw_name,passwd->pw_passwd,passwd->pw_uid,passwd->pw_gid,passwd->pw_gecos,passwd->pw_dir,passwd->pw_shell);
}
我们注意到,想根据用户名或者uid输出对应的行,首先判断用户输入的是uid还是用户名,因为输入字符姐可以转换成id有点模糊,那么这里就用到一个syscall叫做strtol
用来自定义输入的合法值是什么,这个syscall是由ISO提供的
#include <stdlib.h>
long int strtol(const char *nptr,char **endptr,int base);
这里const char *nptr
代表带判断的字符串,char **endptr
代表错误字符串输出的位子,base
代表判断标准
base需要强调一下,他的范围是2 ~ 36,其中base等于10代表合法数值为0 ~ 9,也就是合法值为数字,用户输入错误字符输入到char **endptr
中,
假如base等于11,代表合法值为0 ~ 9(longint)再加上a(a将被转换成 long int),a不区分大小写
假如base等于36,代表合法值为0 ~ 9再加上’a’ ~ ‘z’,字符将被转换成long int返回
所以我们上面使用strtol的时候先是假设用户输入的是数字,调用getpwuid
,为真(返回不为空)就直接返回passwd结构,如果为假(返回值为空)代表getpwnam
失败也就是用户输入的可能不是username是uid,这个时候调用strtol
将base设置为10只有数字合法,然后如果客户输入的数字则strtol
返回的都是数字,然后判断假如strtol
返回不为空,endptr为空,getpwuid(strtol(***))
返回值不为空就正确
回到程序,假设我们的EUID为0,则我们可以看到passwd文件的第二项,也就是hash后的密码,如果不是euid为0那么就看不到
信号
初识信号siguser
我们尝试使用一下signal函数,了解一下他的基本使用,我们下面程序会用到2个基本的信号,分别是SIGUSR1,SIGUSR2,还有SIGUP,前2个分别是自定义信号,后一个是终端接口检测到连接断开,那么这个信号就会发送给控制终端的session leader,最后调用pause函数,阻塞这个进程直到有信号到达为止(其实会一直睡眠,因为在一个死循环中for (;;))
#include <stdio.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
int
sig_user(int signo){
if ( signo == SIGUSR1 ){
printf("nobody catch except SIGUSR1\n");
}else if ( signo == SIGUSR2 ){
printf("nobody catch except SIGUSR2\n");
}else if ( signo == SIGHUP ){
printf("nobody catch except SIGHUB\n");
}
return 0;
}
int
main(void){
if ( (signal(SIGUSR1,sig_user)) == SIG_ERR ){ //sig_usr为函数指针
err(EXIT_FAILURE,"unable to catch SIGUSR1");
}
if ( (signal(SIGUSR2,sig_user)) == SIG_ERR ){
err(EXIT_FAILURE,"unable to catch SIGUSR2");
}
if( (signal(SIGHUP,sig_user)) == SIG_ERR ){
err(EXIT_FAILURE,"unable to catch SIGHUP");
}
(void)printf("%d\n",getpid());
for(;;){
pause();
}
}
我们运行这个文件,打印了当前的pid,并且睡眠(pause)直到信号到来才唤醒
root@workstastion:~# ./a.out
44030
然以我们给这个进程发送信号
kill -USR1 44030
这边程序收到了
root@workstastion:~# ./a.out
44030
nobody catch except SIGUSR1
再发送sigusr2的信号
kill -USR2 44030
程序这边又收到了
root@workstastion:~# ./a.out
44030
nobody catch except SIGUSR1
nobody catch except SIGUSR2
我们再发送一个SIGHUP的信号(也是我们定义捕获的)
kill -HUP 44030
也收到了
root@workstastion:~# ./a.out
44030
nobody catch except SIGUSR1
nobody catch except SIGUSR2
nobody catch except SIGHUB
再发送一个其他程序未定义捕获的信号(大部分信号如果未定义捕获会执行默认操作。大多数默认操作是忽略)
kill -SYS 44030
这里收到这个信号直接core dump
root@workstastion:~# ./a.out
44030
nobody catch except SIGUSR1
nobody catch except SIGUSR2
nobody catch except SIGHUB
Bad system call (core dumped)
SIGSYS这个信号是当你调用一个无效syscall,也许是因为吗,某种原因,机器执行了一条机器指令,内核认为是一条系统调用,但是参数错误
线程
初探线程
我们开始学习线程的时候可能被线程略微奇怪的语法惊到了,我们之前fork一个子进程是用fork返回pid,但是线程id却是先创建一个线程id的变量(pthread_t 也是unsigned long类型)然后这个变量传入pthread_create函数的第一个参数中,进行赋值,线程执行的函数也被传入pthread_create中,我们也可以为这个函数传入参数。。。
我们下面有一个非常小的程序,无限的从一个主线程创建线程
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
pthread_t ntid; //new thread id
void
sig_interrupt(int signo){
if( signo == SIGINT ){
printf("catch signal SIGINT now exit...\n");
exit(EXIT_FAILURE);
}
}
void
printids(const char *s){ //print thread pid etc
pid_t pid;
pthread_t tid;
pid = getpid(); //get current process id
tid = pthread_self(); //get current thread id
printf("%s pid is %lu,thread id is %lu\n",s,(unsigned long)pid,(unsigned long)tid);
}
void *
thread_fun(void *arg){
printids("new thread: ");
return ((void *)0) ; //return NULL string not number 0
}
int
main(void){
if ( (signal(SIGINT,sig_interrupt)) == SIG_ERR ){
fprintf(stderr,"Cant't catch signal SIGINT:%s\n",strerror(errno));
}
printf("wait for main thread\n");
printids("main thread: ");
sleep(1);
printf("========================>\n");
for(;;){
if( (pthread_create(&ntid,NULL,thread_fun,NULL)) != 0 ){
fprintf(stderr,"Can't create new thread:%s\n",strerror(errno));
exit(EXIT_FAILURE);
}
}
exit (EXIT_SUCCESS);
}
编译可能会报错pthread库找不到,我们应该在编译的时候手动的链接pthread库(-lpthread),因为pthread库不是linux标准库
deadlock(死锁)
死锁通俗讲线程T1对资源R1上了写锁,线程T2对资源R2也上了写锁,当T1还没有解锁自己的资源,T2直接对T1的资源R1上锁(无论写锁还是读锁),当然结果是T2被阻塞,在T2被阻塞之后,T1也对T2的资源R2进行上锁(无论写锁还是读锁),这个时候T2没有对自己的资源R2解锁,所以T1也被block了,这样形成一个闭环,2个线程都被锁了,我们班下面的程序创建了一个线程然后让他和主线程形成一个死锁,为什么不创建2个线程让他们相互形成死锁呢?因为主线程任务完成就退出了,主线程一退出根据posix的规矩所有其他的新线程(这个主线程创建的)也会退出,那么虽然2个新线程相互锁上了,但是主线程退出,2个线程也照样退出,从用户端看不出来锁没锁上
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
struct share_for_t1{
char *a;
pthread_rwlock_t lock;
};
struct share_for_t2{
char *a;
pthread_rwlock_t lock;
};
struct share_for_t12{
char *a;
pthread_rwlock_t lock;
};
struct share_for_t1 shared_for_t1 = {"i am a string for shared_for_t1",PTHREAD_RWLOCK_INITIALIZER};
struct share_for_t2 shared_for_t2 = {"i am a string for shared_for_t2", PTHREAD_RWLOCK_INITIALIZER};
struct share_for_t12 shared_for_t12 = {"i am a string for shared_for_t12 ",PTHREAD_RWLOCK_INITIALIZER };
pthread_t t1;
pthread_t t2;
void
initlock(void){
pthread_rwlock_init(&shared_for_t1.lock,NULL);
pthread_rwlock_init(&shared_for_t2.lock,NULL);
pthread_rwlock_init(&shared_for_t12.lock,NULL);
}
void
destorylock(void){
pthread_rwlock_destroy(&shared_for_t1.lock);
pthread_rwlock_destroy(&shared_for_t2.lock);
pthread_rwlock_destroy(&shared_for_t12.lock);
}
void *
t2func(void){
pthread_rwlock_wrlock(&shared_for_t2.lock); //wlock shared_for_t2
printf("after thread %lu pid %d wlock shared_for_t2 now rlock shared_for_t2\n",pthread_self(),getpid());
printf("=========================>\n");
pthread_rwlock_rdlock(&shared_for_t12.lock); //rlock shared_for_t12
printf("now thread %lu read from shared_for_t12\n",pthread_self());
printf("%s\n",shared_for_t12.a);
printf("warning now thread %lu rlock shared_for_t1,this shared now belong to t1,maybe death lock!!!\n",pthread_self());
pthread_rwlock_wrlock(&shared_for_t1.lock); //wlock shared_for_t1,maybe death lock
pthread_rwlock_unlock(&shared_for_t1.lock);
pthread_rwlock_unlock(&shared_for_t12.lock);
}
int
main(void){
initlock;
printf("now pid is %d\n--------------------------------------\n",getpid());
pthread_create(&t2,NULL,t2func,NULL);
/************************main thread lock***************************************************/
pthread_rwlock_wrlock(&shared_for_t1.lock); //wlock shared_for_t1
printf("after thread %lu pid %d wlock shared_for_t1 now rlock shared_for_t1\n",pthread_self(),getpid());
printf("=========================>\n");
sleep(3);
pthread_rwlock_rdlock(&shared_for_t12.lock); //rlock shared_for_t12
printf("now thread %lu read from shared_for_t12\n",pthread_self());
printf("%s\n",shared_for_t12.a);
printf("waring now thread %lu wlock shared_for_t2,this shared now belong to t2 ,mabey death lock!!!\n",pthread_self());
pthread_rwlock_wrlock(&shared_for_t2.lock); //wlock shared_for_t2,maybe death lock
pthread_rwlock_unlock(&shared_for_t1.lock);
pthread_rwlock_unlock(&shared_for_t12.lock);
/***********************main thread lock ****************************************************/
destorylock;
return 0;
}
至于如何去看自己的程序被死锁了,我在stackoverflow上看到说用ps -aux
去看他的状态如果是D
就表明是不可中断状态,进一步说明陷入了死锁,但是我的状态是Sl+
最后我们又用strace -p 指定pid看到他一直在wait,初步判断在死锁中
线程unjoinable属性
线程有2个属性分别是joinable和unjoinable,前一个是线程默认状态,后一个需要我们通过设置attr(pthread_attr_setdetachstate
)去实现,然后创建线程的时候带入这个attr就行,joinable的线程推出或者pthread_exit
后资源不会释放,只有pthread_join获取线程返回值才会释放相应的资源,unjoinable的线程,一退出就会释放资源,如果unjoinable的线程非要pthread_join会出错
下面的程序我们创建了2个线程,分别是joinable和unjoinable的,joinable的线程我们用pthread_join获取了线程返回值,并且回收资源,unjoinable的线程用pthread_join回收资源直接出错
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void *
t1func(void *arg){
printf("tid %ld,attr is joinable\n",pthread_self());
return ((void *) 6); //return char value
}
void *
t2func(void *arg){
printf("tid %ld attr is unjoinable\n",pthread_self());
return ((void *) 5); // return char value
}
int
main(void){
pthread_t tid1,tid2;
pthread_attr_t attr;
void * return_value1;
void * return_value2;
pthread_create(&tid1,NULL,t1func,NULL); //joinable
pthread_attr_init(&attr); //init attr
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //set unjoinable
pthread_create(&tid2,&attr,t2func,NULL); //unjoinable
pthread_join(tid1,&return_value1);
printf("tid1 return value is %ld\n",(long)return_value1);
if( pthread_join(tid2,&return_value2) != 0 ){
printf("get tid2 return value error ,maybe not exist\n");
}
return 0;
}