Linux Device Drivers 2nd Edition (779877), страница 42
Текст из файла (страница 42)
Instead, you should use the following code:loff_t scull_p_llseek(struct file *filp, loff_t off, int whence){return -ESPIPE; /* unseekable */}This function comes from the scullpipe device, which isn’t seekable; the error codeis translated to ‘‘Illegal seek,’’ though the symbolic name means ‘‘is a pipe.’’Because the position indicator is meaningless for nonseekable devices, neitherread nor write needs to update it during data transfer.It’s interesting to note that since pr ead and pwrite have been added to the set ofsupported system calls, the lseek device method is not the only way a user-spaceprogram can seek a file.
A proper implementation of unseekable devices shouldallow normal read and write calls while preventing pr ead and pwrite. This isaccomplished by the following line—the first in both the read and write methodsof scullpipe—we didn’t explain when introducing those methods:if (f_pos != &filp->f_pos) return -ESPIPE;Access Control on a Device FileOffering access control is sometimes vital for the reliability of a device node.
Notonly should unauthorized users not be permitted to use the device (a restriction isenforced by the filesystem permission bits), but sometimes only one authorizeduser should be allowed to open the device at a time.The problem is similar to that of using ttys. In that case, the login process changesthe ownership of the device node whenever a user logs into the system, in orderto prevent other users from interfering with or sniffing the tty data flow. However,it’s impractical to use a privileged program to change the ownership of a deviceevery time it is opened, just to grant unique access to it.None of the code shown up to now implements any access control beyond thefilesystem permission bits. If the open system call forwards the request to thedriver, open will succeed.
We now introduce a few techniques for implementingsome additional checks.Every device shown in this section has the same behavior as the bare scull device(that is, it implements a persistent memory area) but differs from scull in accesscontrol, which is implemented in the open and close operations.16422 June 2001 16:36http://openlib.org.uaAccess Control on a Device FileSingle-Open DevicesThe brute-force way to provide access control is to permit a device to be openedby only one process at a time (single openness). This technique is best avoidedbecause it inhibits user ingenuity. A user might well want to run different processes on the same device, one reading status information while the other is writing data.
In some cases, users can get a lot done by running a few simpleprograms through a shell script, as long as they can access the device concurrently. In other words, implementing a single-open behavior amounts to creatingpolicy, which may get in the way of what your users want to do.Allowing only a single process to open a device has undesirable properties, but itis also the easiest access control to implement for a device driver, so it’s shownhere. The source code is extracted from a device called scullsingle.The open call refuses access based on a global integer flag:int scull_s_open(struct inode *inode, struct file *filp){Scull_Dev *dev = &scull_s_device; /* device information */int num = NUM(inode->i_rdev);if (!filp->private_data && num > 0)return -ENODEV; /* not devfs: allow 1 device only */spin_lock(&scull_s_lock);if (scull_s_count) {spin_unlock(&scull_s_lock);return -EBUSY; /* already open */}scull_s_count++;spin_unlock(&scull_s_lock);/* then, everything else is copied from the bare scull device */if ( (filp->f_flags & O_ACCMODE) == O_WRONLY)scull_trim(dev);if (!filp->private_data)filp->private_data = dev;MOD_INC_USE_COUNT;return 0;/* success */}The close call, on the other hand, marks the device as no longer busy.int scull_s_release(struct inode *inode, struct file *filp){scull_s_count--; /* release the device */MOD_DEC_USE_COUNT;return 0;}Normally, we recommend that you put the open flag scull_s_count (with theaccompanying spinlock, scull_s_lock, whose role is explained in the next16522 June 2001 16:36http://openlib.org.uaChapter 5: Enhanced Char Driver Operationssubsection) within the device structure (Scull_Dev here) because, conceptually,it belongs to the device.
The scull driver, however, uses standalone variables tohold the flag and the lock in order to use the same device structure and methodsas the bare scull device and minimize code duplication.Another Digression into Race ConditionsConsider once again the test on the variable scull_s_count just shown. Twoseparate actions are taken there: (1) the value of the variable is tested, and theopen is refused if it is not 0, and (2) the variable is incremented to mark thedevice as taken. On a single-processor system, these tests are safe because noother process will be able to run between the two actions.As soon as you get into the SMP world, however, a problem arises.
If two processes on two processors attempt to open the device simultaneously, it is possiblethat they could both test the value of scull_s_count before either modifies it.In this scenario you’ll find that, at best, the single-open semantics of the device isnot enforced. In the worst case, unexpected concurrent access could create datastructure corruption and system crashes.In other words, we have another race condition here. This one could be solved inmuch the same way as the races we already saw in Chapter 3. Those race conditions were triggered by access to a status variable of a potentially shared datastructure and were solved using semaphores. In general, however, semaphorescan be expensive to use, because they can put the calling process to sleep.
Theyare a heavyweight solution for the problem of protecting a quick check on a statusvariable.Instead, scullsingle uses a different locking mechanism called a spinlock. Spinlockswill never put a process to sleep. Instead, if a lock is not available, the spinlockprimitives will simply retry, over and over (i.e., ‘‘spin’’), until the lock is freed.Spinlocks thus have very little locking overhead, but they also have the potentialto cause a processor to spin for a long time if somebody hogs the lock. Anotheradvantage of spinlocks over semaphores is that their implementation is emptywhen compiling code for a uniprocessor system (where these SMP-specific racescan’t happen).
Semaphores are a more general resource that make sense onuniprocessor computers as well as SMP, so they don’t get optimized away in theuniprocessor case.Spinlocks can be the ideal mechanism for small critical sections. Processes shouldhold spinlocks for the minimum time possible, and must never sleep while holding a lock.
Thus, the main scull driver, which exchanges data with user space andcan therefore sleep, is not suitable for a spinlock solution. But spinlocks worknicely for controlling access to scull_s_single (even if they still are not theoptimal solution, which we will see in Chapter 9).Spinlocks are declared with a type of spinlock_t, which is defined in<linux/spinlock.h>. Prior to use, they must be initialized:16622 June 2001 16:36http://openlib.org.uaAccess Control on a Device Filespin_lock_init(spinlock_t *lock);A process entering a critical section will obtain the lock with spin_lock:spin_lock(spinlock_t *lock);The lock is released at the end with spin_unlock:spin_unlock(spinlock_t *lock);Spinlocks can be more complicated than this, and we’ll get into the details inChapter 9.
But the simple case as shown here suits our needs for now, and all ofthe access-control variants of scull will use simple spinlocks in this manner.The astute reader may have noticed that whereas scull_s_open acquires thescull_s_lock lock prior to incrementing the scull_s_count flag,scull_s_close takes no such precautions. This code is safe because no other codewill change the value of scull_s_count if it is nonzero, so there will be noconflict with this particular assignment.Restricting Access to a Single User at a TimeThe next step beyond a single system-wide lock is to let a single user open adevice in multiple processes but allow only one user to have the device open at atime.
This solution makes it easy to test the device, since the user can read andwrite from several processes at once, but assumes that the user takes someresponsibility for maintaining the integrity of the data during multiple accesses.This is accomplished by adding checks in the open method; such checks are performed after the normal permission checking and can only make access morerestrictive than that specified by the owner and group permission bits.
This is thesame access policy as that used for ttys, but it doesn’t resort to an external privileged program.Those access policies are a little trickier to implement than single-open policies. Inthis case, two items are needed: an open count and the uid of the ‘‘owner’’ of thedevice. Once again, the best place for such items is within the device structure;our example uses global variables instead, for the reason explained earlier forscullsingle. The name of the device is sculluid.The open call grants access on first open, but remembers the owner of the device.This means that a user can open the device multiple times, thus allowing cooperating processes to work concurrently on the device.